Added twitter, refactored polling services to own class. Fixed \!bot reload losing settings.
This commit is contained in:
parent
69ae607c2a
commit
a37938fbff
1
Pipfile
1
Pipfile
|
@ -13,6 +13,7 @@ google-auth-httplib2 = "*"
|
||||||
google-auth-oauthlib = "*"
|
google-auth-oauthlib = "*"
|
||||||
requests = "*"
|
requests = "*"
|
||||||
igramscraper = "*"
|
igramscraper = "*"
|
||||||
|
twitterscraper = "*"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
pylint = "*"
|
pylint = "*"
|
||||||
|
|
34
README.md
34
README.md
|
@ -126,20 +126,34 @@ Example:
|
||||||
|
|
||||||
* !loc Tampere
|
* !loc Tampere
|
||||||
|
|
||||||
### Instagram
|
### Slow polling services
|
||||||
|
|
||||||
Polls instagram account(s) and posts new items to the room. Uses instagram scraper library
|
These have the same usage - you can add one or more accounts to a room and bot polls the accounts.
|
||||||
without any authentication or api key so it polls only randomly every 30 to 60 minutes to keep traffic at minimum.
|
New posts are sent to room. Polls only randomly every 30 to 60 minutes to keep traffic at minimum.
|
||||||
|
|
||||||
See: https://github.com/realsirjoe/instagram-scraper/
|
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
|
|
||||||
* !ig add [accountname] - Add instagram account to this room (Must be done as room admin)
|
Prefix with selected service, for example "!ig add accountname" or "!twitter list"
|
||||||
* !ig del [accountname] - Delete instagram account from room (Must be done as room admin)
|
|
||||||
* !ig list - List accounts in room
|
* add [accountname] - Add account to this room (Must be done as room admin)
|
||||||
* !ig poll - Poll for new items (Must be done as bot owner)
|
* del [accountname] - Delete account from room (Must be done as room admin)
|
||||||
* !ig clear - Clear all ig accounts from this room (Must be done as room admin)
|
* list - List accounts in room
|
||||||
|
* poll - Poll for new items (Must be done as bot owner)
|
||||||
|
* clear - Clear all accounts from this room (Must be done as room admin)
|
||||||
|
|
||||||
|
#### Instagram
|
||||||
|
|
||||||
|
Polls instagram account(s). Uses instagram scraper library
|
||||||
|
without any authentication or api key.
|
||||||
|
|
||||||
|
See: https://github.com/realsirjoe/instagram-scraper/
|
||||||
|
|
||||||
|
#### Twitter
|
||||||
|
|
||||||
|
Polls twitter account(s). Uses twitter scraper library
|
||||||
|
without any authentication or api key.
|
||||||
|
|
||||||
|
See: https://github.com/taspinar/twitterscraper/tree/master/twitterscraper
|
||||||
|
|
||||||
## Bot setup
|
## Bot setup
|
||||||
|
|
||||||
|
|
12
bot.py
12
bot.py
|
@ -27,7 +27,7 @@ class CommandRequiresOwner(Exception):
|
||||||
|
|
||||||
class Bot:
|
class Bot:
|
||||||
appid = 'org.vranki.hemppa'
|
appid = 'org.vranki.hemppa'
|
||||||
version = '1.1'
|
version = '1.2'
|
||||||
client = None
|
client = None
|
||||||
join_on_invite = False
|
join_on_invite = False
|
||||||
modules = dict()
|
modules = dict()
|
||||||
|
@ -107,6 +107,9 @@ class Bot:
|
||||||
body = event.body
|
body = event.body
|
||||||
if len(body) == 0:
|
if len(body) == 0:
|
||||||
return
|
return
|
||||||
|
if body[0] != '!':
|
||||||
|
return
|
||||||
|
|
||||||
command = body.split().pop(0)
|
command = body.split().pop(0)
|
||||||
|
|
||||||
# Strip away non-alphanumeric characters, including leading ! for security
|
# Strip away non-alphanumeric characters, including leading ! for security
|
||||||
|
@ -150,10 +153,13 @@ class Bot:
|
||||||
traceback.print_exc(file=sys.stderr)
|
traceback.print_exc(file=sys.stderr)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def reload_module(self, modulename):
|
def reload_modules(self):
|
||||||
print('Reloading', modulename)
|
for modulename in bot.modules:
|
||||||
|
print('Reloading', modulename, '..')
|
||||||
self.modules[modulename] = self.load_module(modulename)
|
self.modules[modulename] = self.load_module(modulename)
|
||||||
|
|
||||||
|
self.load_settings(self.get_account_data())
|
||||||
|
|
||||||
def get_modules(self):
|
def get_modules(self):
|
||||||
modulefiles = glob.glob('./modules/*.py')
|
modulefiles = glob.glob('./modules/*.py')
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,7 @@ class MatrixModule:
|
||||||
bot.must_be_admin(room, event)
|
bot.must_be_admin(room, event)
|
||||||
await bot.send_text(room, f'Reloading modules..')
|
await bot.send_text(room, f'Reloading modules..')
|
||||||
bot.stop()
|
bot.stop()
|
||||||
for modulename in bot.modules:
|
bot.reload_modules()
|
||||||
bot.reload_module(modulename)
|
|
||||||
bot.start()
|
bot.start()
|
||||||
elif args[1]=='stats':
|
elif args[1]=='stats':
|
||||||
roomcount = len(bot.client.rooms)
|
roomcount = len(bot.client.rooms)
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
import traceback
|
||||||
|
import sys
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from random import randrange
|
||||||
|
|
||||||
|
class PollingService:
|
||||||
|
known_ids = set()
|
||||||
|
account_rooms = dict() # Roomid -> [account, account..]
|
||||||
|
next_poll_time = dict() # Roomid -> datetime, None = not polled yet
|
||||||
|
service_name = "Service"
|
||||||
|
|
||||||
|
async def matrix_poll(self, bot, pollcount):
|
||||||
|
if len(self.account_rooms):
|
||||||
|
await self.poll_all_accounts(bot)
|
||||||
|
|
||||||
|
async def poll_all_accounts(self, bot):
|
||||||
|
now = datetime.now()
|
||||||
|
for roomid in self.account_rooms:
|
||||||
|
send_messages = True
|
||||||
|
if not self.next_poll_time.get(roomid, None):
|
||||||
|
self.next_poll_time[roomid] = now
|
||||||
|
send_messages = False
|
||||||
|
if now >= self.next_poll_time.get(roomid):
|
||||||
|
accounts = self.account_rooms[roomid]
|
||||||
|
for account in accounts:
|
||||||
|
await self.poll_account(bot, account, roomid, send_messages)
|
||||||
|
|
||||||
|
self.first_run = False
|
||||||
|
|
||||||
|
async def poll_implementation(self, bot, account, roomid, send_messages):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def poll_account(self, bot, account, roomid, send_messages):
|
||||||
|
polldelay = timedelta(minutes=30 + randrange(30))
|
||||||
|
self.next_poll_time[roomid] = datetime.now() + polldelay
|
||||||
|
|
||||||
|
await self.poll_implementation(bot, account, roomid, send_messages)
|
||||||
|
|
||||||
|
|
||||||
|
async def matrix_message(self, bot, room, event):
|
||||||
|
args = event.body.split()
|
||||||
|
|
||||||
|
if len(args) == 2:
|
||||||
|
if args[1] == 'list':
|
||||||
|
await bot.send_text(room, f'{self.service_name} accounts in this room: {self.account_rooms.get(room.room_id) or []}')
|
||||||
|
if args[1] == 'debug':
|
||||||
|
await bot.send_text(room, f'{self.service_name} accounts: {self.account_rooms.get(room.room_id) or []} - known ids: {self.known_ids}')
|
||||||
|
elif args[1] == 'poll':
|
||||||
|
bot.must_be_owner(event)
|
||||||
|
print('forced polling')
|
||||||
|
for roomid in self.account_rooms:
|
||||||
|
self.next_poll_time[roomid] = datetime.now()
|
||||||
|
await self.poll_all_accounts(bot)
|
||||||
|
elif args[1] == 'clear':
|
||||||
|
bot.must_be_admin(room, event)
|
||||||
|
self.account_rooms[room.room_id] = []
|
||||||
|
bot.save_settings()
|
||||||
|
await bot.send_text(room, f'Cleared all {self.service_name} accounts from this room')
|
||||||
|
if len(args) == 3:
|
||||||
|
if args[1] == 'add':
|
||||||
|
bot.must_be_admin(room, event)
|
||||||
|
|
||||||
|
account = args[2]
|
||||||
|
print(f'Adding {self.service_name} account {account} to room id {room.room_id}')
|
||||||
|
|
||||||
|
if self.account_rooms.get(room.room_id):
|
||||||
|
if account not in self.account_rooms[room.room_id]:
|
||||||
|
self.account_rooms[room.room_id].append(account)
|
||||||
|
else:
|
||||||
|
await bot.send_text(room, 'This account already added in this room!')
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self.account_rooms[room.room_id] = [account]
|
||||||
|
bot.save_settings()
|
||||||
|
await bot.send_text(room, f'Added {self.service_name} account {account} to this room.')
|
||||||
|
|
||||||
|
elif args[1] == 'del':
|
||||||
|
bot.must_be_admin(room, event)
|
||||||
|
|
||||||
|
account = args[2]
|
||||||
|
print(
|
||||||
|
f'Removing {self.service_name} account {account} from room id {room.room_id}')
|
||||||
|
|
||||||
|
if self.account_rooms.get(room.room_id):
|
||||||
|
self.account_rooms[room.room_id].remove(account)
|
||||||
|
|
||||||
|
print(
|
||||||
|
f'{self.service_name} accounts now for this room {self.account_rooms.get(room.room_id)}')
|
||||||
|
|
||||||
|
bot.save_settings()
|
||||||
|
await bot.send_text(room, f'Removed {self.service_name} account from this room')
|
||||||
|
|
||||||
|
def get_settings(self):
|
||||||
|
return {'account_rooms': self.account_rooms}
|
||||||
|
|
||||||
|
def set_settings(self, data):
|
||||||
|
if data.get('account_rooms'):
|
||||||
|
self.account_rooms = data['account_rooms']
|
||||||
|
|
||||||
|
def help(self):
|
||||||
|
return(f'{self.service_name} polling')
|
|
@ -2,38 +2,17 @@ import traceback
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from random import randrange
|
from random import randrange
|
||||||
|
from modules.common.pollingservice import PollingService
|
||||||
|
|
||||||
from igramscraper.exception.instagram_not_found_exception import \
|
from igramscraper.exception.instagram_not_found_exception import \
|
||||||
InstagramNotFoundException
|
InstagramNotFoundException
|
||||||
from igramscraper.instagram import Instagram
|
from igramscraper.instagram import Instagram
|
||||||
|
|
||||||
|
class MatrixModule(PollingService):
|
||||||
class MatrixModule:
|
|
||||||
instagram = Instagram()
|
instagram = Instagram()
|
||||||
|
service_name = 'Instagram'
|
||||||
|
|
||||||
known_ids = set()
|
async def poll_implementation(self, bot, account, roomid, send_messages):
|
||||||
account_rooms = dict() # Roomid -> [account, account..]
|
|
||||||
next_poll_time = dict() # Roomid -> datetime, None = not polled yet
|
|
||||||
|
|
||||||
async def matrix_poll(self, bot, pollcount):
|
|
||||||
if len(self.account_rooms):
|
|
||||||
await self.poll_all_accounts(bot)
|
|
||||||
|
|
||||||
async def poll_all_accounts(self, bot):
|
|
||||||
now = datetime.now()
|
|
||||||
for roomid in self.account_rooms:
|
|
||||||
send_messages = True
|
|
||||||
if not self.next_poll_time.get(roomid, None):
|
|
||||||
self.next_poll_time[roomid] = now
|
|
||||||
send_messages = False
|
|
||||||
if now >= self.next_poll_time.get(roomid):
|
|
||||||
accounts = self.account_rooms[roomid]
|
|
||||||
for account in accounts:
|
|
||||||
await self.poll_account(bot, account, roomid, send_messages)
|
|
||||||
|
|
||||||
self.first_run = False
|
|
||||||
|
|
||||||
async def poll_account(self, bot, account, roomid, send_messages):
|
|
||||||
try:
|
try:
|
||||||
medias = self.instagram.get_medias(account, 5)
|
medias = self.instagram.get_medias(account, 5)
|
||||||
for media in medias:
|
for media in medias:
|
||||||
|
@ -53,72 +32,3 @@ class MatrixModule:
|
||||||
|
|
||||||
polldelay = timedelta(minutes=30 + randrange(30))
|
polldelay = timedelta(minutes=30 + randrange(30))
|
||||||
self.next_poll_time[roomid] = datetime.now() + polldelay
|
self.next_poll_time[roomid] = datetime.now() + polldelay
|
||||||
|
|
||||||
async def matrix_message(self, bot, room, event):
|
|
||||||
args = event.body.split()
|
|
||||||
|
|
||||||
if len(args) == 2:
|
|
||||||
if args[1] == 'list':
|
|
||||||
await bot.send_text(room, f'Instagram accounts in this room: {self.account_rooms.get(room.room_id) or []}')
|
|
||||||
elif args[1] == 'poll':
|
|
||||||
bot.must_be_owner(event)
|
|
||||||
for roomid in self.account_rooms:
|
|
||||||
self.next_poll_time[roomid] = datetime.now()
|
|
||||||
await self.poll_all_accounts(bot)
|
|
||||||
elif args[1] == 'clear':
|
|
||||||
bot.must_be_admin(room, event)
|
|
||||||
self.account_rooms[room.room_id] = []
|
|
||||||
bot.save_settings()
|
|
||||||
await bot.send_text(room, 'Cleared all instagram accounts from this room')
|
|
||||||
if len(args) == 3:
|
|
||||||
if args[1] == 'add':
|
|
||||||
bot.must_be_admin(room, event)
|
|
||||||
|
|
||||||
account = args[2]
|
|
||||||
print(f'Adding account {account} to room id {room.room_id}')
|
|
||||||
|
|
||||||
if self.account_rooms.get(room.room_id):
|
|
||||||
if account not in self.account_rooms[room.room_id]:
|
|
||||||
self.account_rooms[room.room_id].append(account)
|
|
||||||
else:
|
|
||||||
await bot.send_text(room, 'This instagram account already added in this room!')
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
self.account_rooms[room.room_id] = [account]
|
|
||||||
|
|
||||||
print(
|
|
||||||
f'Accounts now for this room {self.account_rooms.get(room.room_id)}')
|
|
||||||
|
|
||||||
try:
|
|
||||||
await self.poll_account(bot, account, room.room_id, False)
|
|
||||||
bot.save_settings()
|
|
||||||
await bot.send_text(room, 'Added new instagram account to this room')
|
|
||||||
except InstagramNotFoundException:
|
|
||||||
await bot.send_text(room, 'Account doesn\'t seem to exist')
|
|
||||||
self.account_rooms[room.room_id].remove(account)
|
|
||||||
|
|
||||||
elif args[1] == 'del':
|
|
||||||
bot.must_be_admin(room, event)
|
|
||||||
|
|
||||||
account = args[2]
|
|
||||||
print(
|
|
||||||
f'Removing account {account} from room id {room.room_id}')
|
|
||||||
|
|
||||||
if self.account_rooms.get(room.room_id):
|
|
||||||
self.account_rooms[room.room_id].remove(account)
|
|
||||||
|
|
||||||
print(
|
|
||||||
f'Accounts now for this room {self.account_rooms.get(room.room_id)}')
|
|
||||||
|
|
||||||
bot.save_settings()
|
|
||||||
await bot.send_text(room, 'Removed instagram account from this room')
|
|
||||||
|
|
||||||
def get_settings(self):
|
|
||||||
return {'account_rooms': self.account_rooms}
|
|
||||||
|
|
||||||
def set_settings(self, data):
|
|
||||||
if data.get('account_rooms'):
|
|
||||||
self.account_rooms = data['account_rooms']
|
|
||||||
|
|
||||||
def help(self):
|
|
||||||
return('Instagram polling')
|
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
from twitterscraper import query_tweets_from_user
|
||||||
|
from modules.common.pollingservice import PollingService
|
||||||
|
|
||||||
|
# https://github.com/taspinar/twitterscraper/tree/master/twitterscraper
|
||||||
|
|
||||||
|
class MatrixModule(PollingService):
|
||||||
|
service_name = 'Twitter'
|
||||||
|
|
||||||
|
async def poll_implementation(self, bot, account, roomid, send_messages):
|
||||||
|
for tweet in query_tweets_from_user("twitter", limit=1):
|
||||||
|
if tweet.tweet_id not in self.known_ids:
|
||||||
|
await bot.send_html(bot.get_room_by_id(roomid), f'Twitter <a href="https://twitter.com{tweet.tweet_url}">{account}</a>: {tweet.text}', f'Twitter {account}: {tweet.text} - https://twitter.com{tweet.tweet_url}')
|
||||||
|
self.known_ids.add(tweet.tweet_id)
|
Loading…
Reference in New Issue