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

в 10:48, , рубрики: Debian, linux, open source, python, vk api, Веб-разработка, метки: , , ,

Здравствуйте уважаемыее!
Хотелось бы рассказать от том как я делал смс оповещение для своего факультета.
Я учусь на первом курсе ФСПО ИТМО. У нас на факультете есть так называемая диспетчерская служба которая занимается оповещением студентов об изменении в расписании, в интернете это осуществляется через страничку в контакте и твиттер.
Однажды произошёл небольшой скандал по поводу того что пол-группы не явились на занятия из-за того что не успели отследить изменение в расписании. В тот день у меня появилась идея создать смс оповещение с той самой 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 +79818050567 -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.

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

Исходник можно взять здесь.

Автор: grindel

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js