aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorclarkzjw <[email protected]>2023-02-20 13:47:26 -0800
committerclarkzjw <[email protected]>2023-02-20 13:47:26 -0800
commitf7f99f41e768e6740adbf2ee708488b29fe6265a (patch)
tree125b6ec833af57b2ec1813309506dbe6b791b35f
parent3a230798be1bc63b363cf75b8b1cae3a508cca84 (diff)
downloadswarm2fediverse-f7f99f41e768e6740adbf2ee708488b29fe6265a.tar.gz
implemented basic functions:
- send a location from Telegram to bot - query a list (7) of POIs from Foursquare - send user inline keyboard button to choose a location - post toot status update to Mastodon with a link to OSM - store previously seen locations in local db
-rw-r--r--.gitignore1
-rw-r--r--README10
-rw-r--r--bot.py47
-rw-r--r--config.ini.example20
-rw-r--r--config.py5
-rw-r--r--dbstore/dbm_store.py34
-rw-r--r--foursquare/query_poi.py23
-rw-r--r--mastodon/__init__.py0
-rw-r--r--mastodon/toot.py21
-rw-r--r--toot.py11
10 files changed, 111 insertions, 61 deletions
diff --git a/.gitignore b/.gitignore
index fdccc84..eb701d6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
1foursquare/location_example.json 1foursquare/location_example.json
2config.ini 2config.ini
3.idea/ 3.idea/
4fsq_poi.db
4# Byte-compiled / optimized / DLL files 5# Byte-compiled / optimized / DLL files
5__pycache__/ 6__pycache__/
6*.py[cod] 7*.py[cod]
diff --git a/README b/README
index 6c8ffd4..9b00713 100644
--- a/README
+++ b/README
@@ -1,3 +1,13 @@
1# Swarm2Fediverse 1# Swarm2Fediverse
2 2
3Foursquare Swarm like Telegram bot to checkin at places and post to Fediverse (Mastodon/Pleroma) 3Foursquare Swarm like Telegram bot to checkin at places and post to Fediverse (Mastodon/Pleroma)
4
5TODO:
6
7- Attach up to four images to each checkin
8- Set default checkin visibility to followers-only and add option to set individual checkin visibility
9- Amazon .bot domain
10- i18n
11- Anonymized analysis
12- OAuth login to Mastodon/Pleroma
13- Delayed checkins
diff --git a/bot.py b/bot.py
index e010516..398e433 100644
--- a/bot.py
+++ b/bot.py
@@ -8,6 +8,7 @@ Basic example for a bot that uses inline keyboards. For an in-depth explanation,
8""" 8"""
9import logging 9import logging
10 10
11import telegram.constants
11from telegram import __version__ as TG_VER 12from telegram import __version__ as TG_VER
12 13
13try: 14try:
@@ -26,7 +27,8 @@ from telegram.ext import Application, CallbackQueryHandler, CommandHandler, Cont
26 27
27from config import BOT_TOKEN 28from config import BOT_TOKEN
28from foursquare.query_poi import query_poi 29from foursquare.query_poi import query_poi
29 30from dbstore.dbm_store import get_loc
31from toot import mastodon_client
30 32
31# Enable logging 33# Enable logging
32logging.basicConfig( 34logging.basicConfig(
@@ -36,36 +38,39 @@ logger = logging.getLogger(__name__)
36 38
37 39
38async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: 40async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
39 """Sends a message with three inline buttons attached.""" 41 hello = "Hello, this is `checkin.bot`. \n\n" \
40 keyboard = [ 42 "This is a Telegram bot with functionality similar to Foursquare Swarm, " \
41 [ 43 "but check in and post your location to the Fediverse (Mastodon/Pleroma) instead of Twitter.\n\n" \
42 InlineKeyboardButton("Option 1", callback_data="1"), 44 "Aware of privacy concerns, this bot will not store your location data."\
43 InlineKeyboardButton("Option 2", callback_data="2"), 45 "*Be safe and cautious when sharing your real time location on the web.* \n\n"\
44 ], 46 "Start using this bot by sharing your location using Telegram context menu to it."
45 [InlineKeyboardButton("Option 3", callback_data="3")],
46 ]
47
48 reply_markup = InlineKeyboardMarkup(keyboard)
49 47
50 await update.message.reply_text("Please choose:", reply_markup=reply_markup) 48 await update.message.reply_text(hello, parse_mode=telegram.constants.ParseMode.MARKDOWN)
51 49
52 50
53async def checkin(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: 51async def checkin(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
54 print(update.message.location.latitude) 52 keyboard = []
55 print(update.message.location.longitude) 53
56 poi = query_poi(update.message.location.latitude, update.message.location.longitude) 54 for poi in query_poi(update.message.location.latitude, update.message.location.longitude):
57 await update.message.reply_text("Your location received: {}".format(poi)) 55 keyboard.append([
56 InlineKeyboardButton(poi["name"], callback_data=poi["fsq_id"]),
57 ])
58
59 reply_markup = InlineKeyboardMarkup(keyboard)
60 await update.message.reply_text("Select a place", reply_markup=reply_markup)
58 61
59 62
60async def button(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: 63async def button(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
61 """Parses the CallbackQuery and updates the message text.""" 64 """Parses the CallbackQuery and updates the message text."""
62 query = update.callback_query 65 query = update.callback_query
63 66
64 # CallbackQueries need to be answered, even if no notification to the user is needed
65 # Some clients may have trouble otherwise. See https://core.telegram.org/bots/api#callbackquery
66 await query.answer() 67 await query.answer()
68 poi = get_loc(query.data)
69
70 mastodon_client.status_post(f"I'm at {poi['name']} in {poi['locality']}, {poi['region']}, \n[OSM]({poi['osm_url']})",
71 visibility="private")
67 72
68 await query.edit_message_text(text=f"Selected option: {query.data}") 73 await query.edit_message_text(text=f"Selected option: {poi}")
69 74
70 75
71async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: 76async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
@@ -74,14 +79,10 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
74 79
75 80
76def main() -> None: 81def main() -> None:
77 """Run the bot."""
78 # Create the Application and pass it your bot's token.
79 application = Application.builder().token(BOT_TOKEN).build() 82 application = Application.builder().token(BOT_TOKEN).build()
80 83
81 application.add_handler(CommandHandler("start", start)) 84 application.add_handler(CommandHandler("start", start))
82 application.add_handler(CommandHandler("checkin", checkin))
83 application.add_handler(CallbackQueryHandler(button)) 85 application.add_handler(CallbackQueryHandler(button))
84 # on non command i.e message - echo the message on Telegram
85 application.add_handler(MessageHandler(filters.LOCATION & ~filters.COMMAND, checkin)) 86 application.add_handler(MessageHandler(filters.LOCATION & ~filters.COMMAND, checkin))
86 application.add_handler(CommandHandler("help", help_command)) 87 application.add_handler(CommandHandler("help", help_command))
87 88
diff --git a/config.ini.example b/config.ini.example
index f803e56..4c29644 100644
--- a/config.ini.example
+++ b/config.ini.example
@@ -1,15 +1,9 @@
1[DEFAULT] 1[DEFAULT]
2BOT_TOKEN="" 2BOT_TOKEN =
3FOURSQUARE_API_KEY =
3 4
4; [DEFAULT] 5[TOOT]
5; ServerAliveInterval = 45 6CLIENT_ID =
6; Compression = yes 7CLIENT_SECRET =
7; CompressionLevel = 9 8API_BASE_URL = https://mastodon.social
8; ForwardX11 = yes 9ACCESS_TOKEN =
9
10; [forge.example]
11; User = hg
12
13; [topsecret.server.example]
14; Port = 50022
15; ForwardX11 = no
diff --git a/config.py b/config.py
index 377a6c3..a403db0 100644
--- a/config.py
+++ b/config.py
@@ -6,3 +6,8 @@ config.read("config.ini")
6 6
7BOT_TOKEN = config["DEFAULT"]["BOT_TOKEN"] 7BOT_TOKEN = config["DEFAULT"]["BOT_TOKEN"]
8FSQ_API_KEY = config["DEFAULT"]["FOURSQUARE_API_KEY"] 8FSQ_API_KEY = config["DEFAULT"]["FOURSQUARE_API_KEY"]
9
10TOOT_API_BASE_URL = config["TOOT"]["API_BASE_URL"]
11TOOT_CLIENT_ID = config["TOOT"]["CLIENT_ID"]
12TOOT_CLIENT_SECRET = config["TOOT"]["CLIENT_SECRET"]
13TOOT_ACCESS_TOKEN = config["TOOT"]["ACCESS_TOKEN"]
diff --git a/dbstore/dbm_store.py b/dbstore/dbm_store.py
new file mode 100644
index 0000000..fedb505
--- /dev/null
+++ b/dbstore/dbm_store.py
@@ -0,0 +1,34 @@
1import dbm
2
3db = None
4store_file = "fsq_poi.db"
5
6
7def get_loc(fsq_id):
8 global db
9 if db is None:
10 db = dbm.open(store_file, 'c')
11 if fsq_id in db:
12 res = db[fsq_id].decode("utf-8").split("|")
13 return {
14 "name": res[0],
15 "locality": res[1],
16 "region": res[2],
17 "latitude": res[3],
18 "longitude": res[4],
19 "osm_url": res[5],
20 }
21 else:
22 return None
23
24
25def store_loc(loc):
26 global db
27 if db is None:
28 db = dbm.open(store_file, 'c')
29 db[loc["fsq_id"]] = "{}|{}|{}|{}|{}|{}".format(loc["name"],
30 loc["locality"],
31 loc["region"],
32 loc["latitude"],
33 loc["longitude"],
34 loc["osm_url"])
diff --git a/foursquare/query_poi.py b/foursquare/query_poi.py
index 64ded55..efdd1b1 100644
--- a/foursquare/query_poi.py
+++ b/foursquare/query_poi.py
@@ -1,18 +1,33 @@
1import requests 1import requests
2import json 2import json
3from config import FSQ_API_KEY 3from config import FSQ_API_KEY
4from dbstore.dbm_store import get_loc, store_loc
4 5
5POI_API_ENDPOINT = "https://api.foursquare.com/v3/places/nearby?ll={}%2C{}" 6POI_API_ENDPOINT = "https://api.foursquare.com/v3/places/nearby?ll={}%2C{}"
6 7OSM_ENDPOINT = "https://www.openstreetmap.org/?mlat={}&mlon={}&zoom=15&layers=M"
7 8
8def query_poi(latitude, longitude): 9def query_poi(latitude, longitude):
9 url = POI_API_ENDPOINT.format(latitude, longitude) 10 locations = list()
10 11
12 url = POI_API_ENDPOINT.format(latitude, longitude)
11 headers = { 13 headers = {
12 "accept": "application/json", 14 "accept": "application/json",
13 "Authorization": FSQ_API_KEY 15 "Authorization": FSQ_API_KEY
14 } 16 }
15 17
16 response = requests.get(url, headers=headers) 18 response = requests.get(url, headers=headers)
17 print(response.text) 19
18 return json.loads(response.text)["results"][:2] 20 for poi in json.loads(response.text)["results"]:
21 loc = {
22 "fsq_id": poi["fsq_id"],
23 "name": poi["name"],
24 "locality": poi["location"]["locality"],
25 "region": poi["location"]["region"],
26 "latitude": poi["geocodes"]["main"]["latitude"],
27 "longitude": poi["geocodes"]["main"]["longitude"],
28 "osm_url": OSM_ENDPOINT.format(poi["geocodes"]["main"]["latitude"], poi["geocodes"]["main"]["longitude"])
29 }
30 locations.append(loc)
31 store_loc(loc)
32
33 return locations
diff --git a/mastodon/__init__.py b/mastodon/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/mastodon/__init__.py
+++ /dev/null
diff --git a/mastodon/toot.py b/mastodon/toot.py
deleted file mode 100644
index 2d07f0b..0000000
--- a/mastodon/toot.py
+++ /dev/null
@@ -1,21 +0,0 @@
1from mastodon import Mastodon
2
3'''
4https://mastodonpy.readthedocs.io/en/stable/index.html
5
6Mastodon.create_app(
7 'pytooterapp',
8 api_base_url = 'https://mastodon.social',
9 to_file = 'pytooter_clientcred.secret'
10)
11'''
12
13# mastodon = Mastodon(client_id = 'pytooter_clientcred.secret',)
14# mastodon.log_in(
15# '[email protected]',
16# 'incrediblygoodpassword',
17# to_file = 'pytooter_usercred.secret'
18# )
19
20mastodon = Mastodon(access_token = 'pytooter_usercred.secret')
21mastodon.toot('Tooting from Python using #mastodonpy !') \ No newline at end of file
diff --git a/toot.py b/toot.py
new file mode 100644
index 0000000..f1db858
--- /dev/null
+++ b/toot.py
@@ -0,0 +1,11 @@
1from mastodon import Mastodon
2from config import TOOT_API_BASE_URL, TOOT_CLIENT_SECRET, TOOT_ACCESS_TOKEN, TOOT_CLIENT_ID
3
4'''
5https://mastodonpy.readthedocs.io/en/stable/index.html
6'''
7
8mastodon_client = Mastodon(client_id=TOOT_CLIENT_ID,
9 client_secret=TOOT_CLIENT_SECRET,
10 api_base_url=TOOT_API_BASE_URL,
11 access_token=TOOT_ACCESS_TOKEN)
Powered by cgit v1.2.3 (git 2.41.0)