Смс оповещение для студентов со странички вконтакте

  • Tutorial
Здравствуйте уважаемые хабровчане!
Хотелось бы рассказать от том как я делал смс оповещение для своего факультета.
Я учусь на первом курсе ФСПО ИТМО. У нас на факультете есть так называемая диспетчерская служба которая занимается оповещением студентов об изменении в расписании, в интернете это осуществляется через страничку в контакте и твиттер.
Однажды произошёл небольшой скандал по поводу того что пол-группы не явилось на занятия из-за того, что не успели отследить изменение в расписании. В тот день у меня появилась идея создать смс оповещение с той самой Vk странички.
Да, я знаю, что для это есть специализированные сервисы, но это слишком просто…



Лирическое отступление

Я новичок в Linux, в Phython и Sql и статья эта пишется по принципу «Учась учи».
Так что не серчайте сильно, если что не так, а лучше просто отпишите свои замечания в комментариях.

С чего начать?


Немного поразмыслив я понял что мне нужно:
  • Сканировать Vk страничку на предмет новых постов
  • Искать в этих самых постах упоминания о какой либо группе, например 143
  • Если в каком либо посте найден номер какой либо группы, то разослать этот пост на мобильники этой группы

У меня есть домашний «сервер» на Debian который я гордо именую GrindelServer, на его базе я и собирался всё это реализовать.
Решено было начать с того что-бы просто научится отправлять смски из командной строки. Так получилось что у меня завалялось около трех 3G модемов Huawei E1150, решено было использовать один из них. В Google без труда нашёлся материал по этой теме. Вкратце нужно было перевести модем в режим «только модем» (так у них еще есть фэйковый CD привод и карт ридер), после чего настроить программу Gnokii или Gammu на работу с ним.
Я выбрал Gammu…

Gammu


Не могу сказать что с Gammu было просто, но в итоге всё получилось.

Установка в Debian стандартная:
$ sudo apt-get install gammu

У меня поставилась версия 1.28.0
Конфиг gammu: /etc/gammurc
Модем обычно определяется как /dev/ttyUSB0, ttyUSB1, ttyUSB2. Нам нужен ttyUSB0
В конфиг достаточно добавить
[gammu]
port= /dev/ttyUSB0
connection = at

Сonnectinon — это тип соединения

Всё! после этого можно проверять
$ sudo gammu  --identify

Должно выдать что-то на подобии
Устройство : /dev/ttyUSB0
Manufacturer         : huawei
Модель         : unknown (E1550)
Firmware             : 11.608.12.00.143
IMEI                 : 123456789101112
Номер SIM (IMSI) : 123456789098765

Здесь проблем возникнуть не должно, но если они есть, то всегда есть $ man gammu и Google.

Первая смс из консоли

Cначала нужно задать права на использование модема, они понадобятся только для этого примера, так что не переживайте по тому поводу, что они сбрасываются после перезагрузки.
$ sudo chmod 777 /dev/ttyUSB0

Теперь, если в модем вставлена симка и на ней есть деньги, можно отправить смс
Например:
$ sudo echo "Привет Grindel" | gammu sendsms TEXT +79811111111 -unicode


Автоматизация отправки смс

Пакет gammu в данном случае нужен был только для проверки, в дальнейшем я использовал демон gammu-smsd.
Он умеет отсылать смс-ки из базы данных (подробно о нём можно почитать на сайте gammu, но обратите внимание на то, что там описывается более поздняя версия.)
Ставим:
$ sudo apt-get install gammu-smsd

Конфиг /etc/gammu-smsdrc
[gammu]
port = /dev/ttyUSB0
connection = at
[smsd]
#Сервер базы данных
service = MYSQL
host = localhost
logfile = /var/log/gammu-smsd
#Пользователь базы
user = smsd
password = password
#Адрес сервера базы данных
pc = localhost
#Имя базы
database = smsd

Теперь нужно создать базу.
В $ man gammu-smsd-tables сказано что дамп необходимой базы находится по адресу docs/sql/mysql.sql насколько я понял надо искать в исходниках программы, так что скачав их(версия 1.28.0) можно распаковать архив и в нем найти необходимый sql скрипт, а дальше залить его любым удобным образом, например через phpMyAdmin.(Если кто-нибудь предложит более разумный метод, буду рад!)

После всех этих действий стоит перезапустить демон
$ sudo /etc/init.d/gammu-smsd restart

ВНИМАНИЕ! После запуска gammu-smsd, gammu --identify станет выдавать ошибку, это нормально!
Отправка смс через mysql

Пример из $ man gammu-smsd-tables, этот код можно исполнить в том же phpMyAdmin
INSERT INTO outbox (
	DestinationNumber,
	TextDecoded,
	CreatorID,
	Coding
) VALUES (
	 'НОМЕР ТЕЛЕФОНА',
	'This is a SQL test message',
	 'Program',
	 'Default_No_Compression'
);

Принцип следующий
  • DestinationNumber — это номер на который должна прийти смс
  • TextDecoded — текст который должен прийти
  • CreatorID — просто пометка, говорящая о там какой процесс создал эту запись
  • Coding — кодировка, если смс на английском то указывается 'Default_No_Compression', если на русском то следует указать 'Unicode_No_Compression'.

ВНИМАНИЕ! В смс можно отправить только 160 латинских и 70 кириллических символов! Для отправки большего количества текста существуют «multipart sms». Gammu-smsd поддерживает их отправку, процесс описан в $ man gammu-smsd-tables

Написание своей программы для проверки vk странички


Честно говоря дальше я был в полной растерянности, я никогда не писал подобных программ, весь мой опыт программирования ограничивался школьным Turbo Pascal и небольшим js проектом «Bart Chalkboard Generator»(можно найти в моём профиле).
Я даже не знал какой язык выбрать. Решено было задать вопрос в Q&A.
Спасибо большое за ответы g0lden и avalak!
В результате я выбрал Python и приступил к его изучению на Codecademy. Параллельно я искал способ реализации работы с VK API на Python. И нашёл я его на Хабре, спасибо dzhioev за это! Так же я потихоньку начал разбираться с VK api. Из-за учёбы свободного времени оставолось очень мало и продвигался я очень медленно.

Познакомившись с Python более или менее(на Codecademy я дошёл до списков и словарей), я решил что уже готов приступить к написанию своего скрипта.

Начал я с регистрации странички вконтакте от имени которой будет действовать приложение, далее нужно было зарегистрировать своё приложение. Это достаточно просто и описано в документации вконтакта, так что писать об этом не буду.

Разбор скрипта

Вкратце последовательность действий скрипта такова:
  • Узнать id последнего отправленного программой поста
  • Узнать появлялись ли новые посты после последней отправки
  • Попытаться найти в каждом из новых постов хотя бы одну из групп
  • Если был обнаружен номер группы то отправить смс по номерам групп(то есть отправить определённый записи в таблицу outbox базы smsd)

Стоит пояснить про базу данных. Я добавил в базу smsd таблицы VkPosts и Groups. В VkPosts складываются id всех оправленных программой постов, так что в следующий раз можно запросить id последнего отправленного поста, соответственно самого позднего. А в groups находятся номера студентов и номера групп: группа | номер.

Теперь сам скрипт по кусочкам

Подключаем необходимые модули
import vk_auth
import json
import urllib2
from urllib import urlencode
import MySQLdb
import logging
import time
import re


Инициализируем соединение с базой, и задаём кодировку, что бы не было кракозябр
db = MySQLdb.connect(host="localhost", user="Логин пользователя бд", passwd="Пароль", db="smsd")
cursor = db.cursor()
db.set_character_set('utf8')
cursor.execute('SET NAMES utf8;')
cursor.execute('SET CHARACTER SET utf8;')
cursor.execute('SET character_set_connection=utf8;')


Сообщаем vk api логин, пароль, номер приложения и запрашиваем привилегии с помощью функции auth из модуля vk_auth, напоминаю модуль взят отсюда.
token, user_id = vk_auth.auth(login, password, "3139526", "groups,wall")

Получаем в переменную token токен приложения а в user_id айди пользователя чьи данный были введены.

Далее определяем ещё несколько переменных
cursor.execute("SELECT postID FROM  VKposts ORDER BY  VKposts.number DESC LIMIT 0 , 1;") #Получаем id последнего отправленного поста
lastSendedPostId = cursor.fetchall()[0][0] #Кладём его в LastSendedPostId в нормальном виде
groupId = -123456 #Указываем Id группы, ВНИМАНИЕ id группы необходимо указать со знаком минус иначе vkapi решит что это id странички пользователя
cursor.execute("SELECT DISTINCT `group` FROM `groups`") #Собираем массив номеров групп
rawGroups = cursor.fetchall()
groups = []
for rawGroup in rawGroups:
    groups.append(rawGroup[0])


Основная часть программы
post = call_api("wall.get", [("owner_id", groupId), ("count", "1")], token)[1] #Получаем последний пост ос стены группы
if post['id'] > lastSendedPostId and post['date'] > actualTime: #Сравниваем Id только что полученого поста с Id последнего отправленного, да можно обойтись без это строчки, но мне так проще было понять
    logging.info('I have found some new posts!')
    i = 0
    while post['id'] > lastSendedPostId and post['date'] > actualTime: #Выбираем в цикле все новые посты
        if not len(post) > 1: #Это будет верно если посты на стене внезапно закончатся
            break
        post['text'] = post['text'].replace('<br>', ' ') #Убираем из текста поста ненужные нам теги перевода строк
        for group in groups: #Перебираем массив с номерми групп
            if len(re.findall("(^| )" + str(group) + "( |$)", post['text'])) != 0: #Если в тексте поста есть больше нуля совпадение по регулярному выражению "(^| )" + номер группы + "( |$)"
                logging.info("I have found %s group in %s post" % (group, post['id']))
                cursor.execute("SELECT `tel` FROM `groups`WHERE `group`=%s;" % (group)) #То получаем номера телефонов этой группы
                rawTels = cursor.fetchall()
                tels = []
                for rawTel in rawTels:
                    tels.append(rawTel[0])
                for tel in tels: #Перебираем полученный массив телефонов
                    if len(post['text']) > 70: #Если длинна текста поста больше 70 то посылаем замену с указанием ссылки на пост
                        cursor.execute("INSERT INTO outbox(DestinationNumber, Coding, TextDecoded, CreatorID, Class)VALUES ('%s', 'Unicode_No_Compression', 'У тебя изменилось расписание vk.com/wall%s_%s', 'Python', '-1')" % (tel, groupId, post['id']))
                        sendedPosts.insert(0, post['id']) #Добавляем Id поста в массив
                        logging.info("Send Cuted %s post to %s****" % (post['id'], tel[0:8]))
                    else: #Если нет то тогда посылвем полный текст поста
                        cursor.execute("INSERT INTO outbox(DestinationNumber, Coding, TextDecoded, CreatorID, Class)VALUES ('%s', 'Unicode_No_Compression', '%s', 'Python', '-1')" % (tel, post['text']))
                        sendedPosts.insert(0, post['id']) #Добавляем Id поста в массив
                        logging.info("Send %s post to %s****" % (post['id'], tel[0:8]))
        i += 1
        post = call_api("wall.get", [("owner_id", groupId), ("offset", i), ("count", "1")], token)[1] #Получаем следующий пост

Здесь есть небольшая загвоздка. Если текст поста больше 70 символов я отправляю вместо него — «У тебя изменилось расписание » + сслыка на пост, делаю я это потому что в одну кириллическую смс не влезет больше 70 символов(было сказано выше), а с multipart sms я ещё не разобрался.
logging.info это функция функция ведения логов настроенная в начале кода:
logging.basicConfig(format='%(asctime)s | %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p', filename='/home/NetDisk/sms/smslog.log',level=logging.DEBUG)

Вот собственно и всё! Осталось сделать скрип исполняемым
$ sudo chmod +x путь к скрипту

И добавить его в Cron c требуемым интервалом.
Но возможности gammu и всего выше перечисленного на этом не заканчиваются, и ваши, я уверен, тоже. Так что фантазируйте.

В планах


  • Сбор базы данных студентов
  • Создание веб интерфейса
  • И еще всякие мелочёвки и фитчи


Источники




P.S.


Мне было очень интересно всем эти заниматься, я был полностью захвачен этой идеей, я был счастлив иметь возможность все это делать и изучать, пусть не всегда было легко(иной раз хотелось всё бросить).
Спасибо, что прочли, надеюсь вам понравилось!
Удачи вам с вашими идеями!

Исходник можно взять здесь.
Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 21

    +1
    Идея полезная. Спасибо за подробное описание.
    Но есть одно «но», успешность реализации упирается в «человеческий фактор», в то, насколько получатели заинтересованы следовать полученным в смс указаниям. Т.е. вопрос в том, насколько эффективна будет описанная система, если у аудитории получающей смс будет желание свалить свои «прогулы» на некорректную её работу (некорректную работу провайдеров/операторов).
      0
      Это понятно, но я всё это делал больше что бы научится, для саморазвития так сказать.
      Что бы не было возможности свалить на некорректурную работу, буду вести логи всего, что-только можно, а потом и восве думаю передать систему тех поддержки факультета.
      А то что кто-то будет это игнорировать меня не волнует, эти их дело.
      0
      Если бы я был студентом, мне бы не захотелось давать свой твиттер и контакт деканату.
        0
        Ну, или номер сотового телефона ))
          0
          Вы немного не дополняли видимо, студенты контакт и твиттер свой не дают, они могут быть просто подписанны на эти странички.
          К тому же у нас на факультете и так все со всеми дружат в VK.
          А база и так уже есть.
            0
            Ну вообще-то сейчас при поступлении просят номер сотового. А заполнять или нет уже дело каждого. Мне за все время обучения позвонили всего один раз.
              0
              Да.
              Но у меня поле мобильный было со звездочкой)
              Еще телефоны родителей просят.
          +1
          Ещё можно завести расшаренный гуглокалендарь на группу (в личном кабинете студента недавно прикрутили наконец экспорт расписания). Гуглкалендарь тоже умеет рассылать смс, но вроде бы это нельзя сделать без активных действий с принимающей стороны.
            0
            Такая рассылка смс получается бесплатной? Сервисы рассылок просят денюжку.
              0
              Присоединяюсь к предыдущему вопросу — кто платит за СМС? Т.е. техническая реализация понятно, а экономическая?
                0
                Вероятно, владелец сим-карты, которая в GSM-модеме.
                0
                Финансовым вопросом будет заниматься факультет.
                  0
                  Если при каждом изменении расписания на факультете будет отправка смски всем студентам группы, то факультет скорее всего разорится. Идея классная, возможно стоит подумать реализацию этого механизма с точки зрения организации. Например смску можно отправлять старосте группы.
                    0
                    Есть безлимитные тарифы
                • UFO just landed and posted this here
                    0
                    Думаю над этим. Просто когда делал хотелось всё своё)
                    • UFO just landed and posted this here
                        0
                        Думаю можно будет пользоваться одним из платных сервисов(цены у них смешные) имеющим свой API.
                          0
                          Ой, беру свои слова назад, они тоже какие-то жадные…
                    0
                    Там ограничение в 1 минуту:(
                      +1
                      Недавно озаботился подобным вопросом.
                      Условия немного другие: у нашей студенческой группы есть общий твиттер-аккаунт, каждый может сообщить какую-то новость. Идея была следующей: отправлять твиты с определённым хеш-тегом в виде смс-оповещений.

                      Остановился на связке: RSS + триггер на IFTTT.com.
                      Плюсы:
                      — бесплатно
                      — легко настроить

                      Минусы:
                      — задержка смс-уведомлений (от 20 минут до часа).

                      Описал рецепт у себя в блоге.

                      Only users with full accounts can post comments. Log in, please.