Skip to content

Commit 09fafde

Browse files
author
Pavlo Hrab
committed
Initial release
0 parents  commit 09fafde

File tree

4 files changed

+312
-0
lines changed

4 files changed

+312
-0
lines changed

Procfile

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
web: python3 bot.py

README.md

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Background
2+
3+
This is a telegram bot, that sends text notes to the daily documents (or in a separate folder, just tagging date) in [remnote](https://www.remnote.io/). If the text contains a link, then this link will be extracted and used as a source for rem. Any additional notes, following the created rem, are possible.
4+
5+
The bot is intended to be run from [heroku](https://heroku.com) on a free tier. The installation guide below describes how to get the bot running.
6+
7+
# Features
8+
1. **Send text notes** directly to the daily page, or in a separate folder, still referencing today's page
9+
2. **Share websites** to the remnote (the same as text notes). If a link is found within the text, then it becomes a source, and page title becomes the rem text
10+
3. **Add** any subsequent **notes to the created** rem (tight after the creatin). All the notes will put inside created rem.
11+
4. **Tags** are only supported if they are one word ( underscore delimiter can be also used). In this case they can be added via ##example_tag while making a note.
12+
5. **References** are encapsulated in [[example reference]]. They can consist of several words.
13+
14+
**For example**: The one can share a link to a bot and follow it with several text messages, describing the ideas described in the website. Maybe "##Idea" can be tagged or [[example project]] referenced.
15+
16+
# Installation
17+
18+
1. Create a new bot via the @BotFather bot in Telegram. Just type `/newbot` command and follow the instructions
19+
2. Install [Git](https://git-scm.com/downloads) and [Heroku CLI](https://devcenter.heroku.com/articles/getting-started-with-python#set-up)
20+
3. Clone this repo locally and go to the folder. Just type in terminal (or git bash) `git clone && cd`
21+
4. Then initialize an empty repo `git init`
22+
5. Create a Heroku app.
23+
1. `heroku login` <- login to the Heroku CLI. If you don't have an account on Heroku, create one.
24+
2. `heroku create` <- create an app
25+
6. Make API key for remnote (Make sure you registered in RemNote (; )). Please go [here](https://www.remnote.io/api_keys) and add a new API key. **Leave** "Enter the name of the top-level folder to restrict this API key to. (Leave this empty for full access.)" field **empty**. Then Name the Key as you wish.
26+
7. Open `bot.py` file in any text editor (in a locally cloned copy of a repo) and add the following fields:
27+
1. `TOKEN = ''` <- This is Telegram bot token. It must have been given from @BotFather bot. To remind it, you can list created bots via `/mybots` -> `<your-bot-name>` -> `API Token`
28+
2. `REMNOT_API= ''` <- This is created RemNote API key (in step 6).
29+
3. `USER_ID = ''` <- User Id can be obtained from [this](https://www.remnote.io/api_keys) page, where you have created your RemNote API. Copy the value from `User ID` column.
30+
4. `HEROKU_NAME = ''` <- is the name of your Heroku app when you typed `heroku create`. Paste it in the format `https://yourherokuappname.herokuapp.com/`.
31+
5. `HOME_DIR = 'Saved Telegram'` <- Name of a directory (rem) where to save content. You can provide any name you want. If no Rem will be found, it will be created
32+
8. Save the `bot.py` file.
33+
9. From a terminal in a repo directory type `heroku git:remote -a YourAppName`, where your app name should be obtained from the created link - `https://yourherokuappname.herokuapp.com/`
34+
10. Type `git push heroku master`
35+
11. (optional) Go to the @BotFather bot in a Telegram. Go to the `/mybots` -> `<your-bot-name>` -> `Edit Bot` -> `Edit Command` and type `stop - stop adding notes`. Then in the same `Edit Bot` menu go to the `Edit Botpic` and upload the picture you like. I used [this](https://drive.google.com/file/d/1_6PxFeHHWRDj26UIpuwcUsKam_FN6XSv/view) official one. All the official materials are available [here](https://www.remnote.io/a/remnote-media-kit/5fd4ff11c3785c0045946db7)
36+
12. Congrats, all is done!
37+
38+
# Usage
39+
40+
The only input, that is needed from the user, is a sent text message. Then the conversation dialogue via incline keyboard will begin:
41+
1. Firstly, the user can choose if to save note directly to the daily document, or save it separately
42+
2. Secondly, the user can add some following notes. You can add as many notes as you wish. Every message will be treated as separate rem. Links will not be extracted in this mode and would be treated as text. If you finished adding notes, then send the `/stop` command.
43+
44+
Everything regarding how to use tags and references is in the **Features** section. If any API changes will be made, then this bot will change and evolve (:
45+
46+
# What free Heroku tier means
47+
Because the bot is hosted on a free Heroku tier, it will go asleep after 30 min of inactivity. What that really means is that it can take up to 1 min for the bot to wake up after sending a message (after a long inactivity period). To my mind, it makes no big deal, but if it bothers you, then you can use [Kaffeine](https://kaffeine.herokuapp.com) app. But do not forget then to add some more free [dynos](https://www.heroku.com/dynos) (+400) via adding a credit card to your account!

bot.py

+261
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
# Import some libraries
2+
import logging
3+
from bs4 import BeautifulSoup
4+
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ReplyKeyboardMarkup
5+
from telegram.ext import *
6+
import os, json, requests, re
7+
from datetime import datetime as dt
8+
import datetime
9+
10+
11+
# USER defined variables
12+
TOKEN = ''
13+
REMNOT_API= ''
14+
USER_ID = ''
15+
HEROKU_NAME = ''
16+
HOME_DIR = 'Saved Telegram'
17+
18+
# Some global variables
19+
PORT = int(os.environ.get('PORT', 5000))
20+
NOTE_ID = None
21+
PARENT = None
22+
NOTE = None
23+
MEDIA_ID = None
24+
FIRST, SECOND = range(2)
25+
26+
# Enable logging
27+
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
28+
level=logging.INFO)
29+
30+
logger = logging.getLogger(__name__)
31+
32+
# Get the todays date!
33+
def suffix(d):
34+
return 'th' if 11<=d<=13 else {1:'st',2:'nd',3:'rd'}.get(d%10, 'th')
35+
36+
def custom_strftime(format, t):
37+
return t.strftime(format).replace('{S}', str(t.day) + suffix(t.day))
38+
39+
today = custom_strftime('%B {S}, %Y', dt.now())
40+
41+
# Send notes
42+
def send_note(text, parent=None, link=False, doc=None):
43+
global REMNOT_API, USER_ID, DOCUMENT, MEDIA_ID, today, URL
44+
url_post="https://api.remnote.io/api/v0/create"
45+
parent_Id = parent
46+
if parent == None:
47+
parent_Id = get_daily_rem()
48+
if parent_Id == None:
49+
parent_Id = search_parent()
50+
text = text + "#[["+today+"]]"
51+
par={
52+
"apiKey": REMNOT_API,
53+
"userId":USER_ID,
54+
"text": text,
55+
"parentId": parent_Id
56+
}
57+
if link == True:
58+
par['isDocument'] = True
59+
par['source'] = URL
60+
reqs = requests.get(URL)
61+
soup = BeautifulSoup(reqs.text, 'html.parser')
62+
par['text'] = soup.find_all('title')[0].get_text()
63+
elif DOCUMENT==True:
64+
par['source'] = open(f"{MEDIA_ID}.pdf", 'rb')
65+
par['isDocument'] = True
66+
par['text'] = MEDIA_ID
67+
created_rem = requests.post(url_post, data=par)
68+
return created_rem.json()['remId']
69+
70+
def get_daily_rem():
71+
global REMNOT_API, USER_ID
72+
url_get_by_name = "https://api.remnote.io/api/v0/get_by_name"
73+
search={
74+
"apiKey": REMNOT_API,
75+
"userId":USER_ID,
76+
"name":datetime.date.today().strftime('%d/%m/%Y')}
77+
res = requests.post(url_get_by_name, data=search)
78+
if res.json()["found"] == True:
79+
return res.json()["_id"]
80+
else:
81+
return None
82+
83+
84+
85+
# Start the conversation
86+
87+
def start_doc(update, context):
88+
global DOCUMENT, LINK, MEDIA_ID
89+
DOCUMENT = True
90+
LINK = False
91+
media = context.bot.get_file(update.message.document)
92+
photo = context.bot.get_file(media.file_id)
93+
MEDIA_ID = media.file_id
94+
photo.download(f"{media.file_id}.pdf")
95+
if PARENT != None:
96+
send_note(text=update.message.text, parent=PARENT, doc=media.file_id)
97+
else:
98+
update.message.reply_text("Need some extra details...",
99+
reply_markup=main_menu_keyboard())
100+
return FIRST
101+
102+
def start(update, context):
103+
"""Echo the user message."""
104+
global LINK, DOCUMENT, NOTE, PARENT, URL
105+
DOCUMENT = False
106+
NOTE = update.message.text
107+
try:
108+
text_to_search = str(NOTE).rsplit()
109+
URL=None
110+
for i in text_to_search:
111+
if URL != None:
112+
break
113+
else:
114+
try:
115+
URL = re.search("(?P<url>https?://[^\s]+)", str(i)).group("url")
116+
except:
117+
pass
118+
requests.get(URL)
119+
LINK = True
120+
except:
121+
LINK = False
122+
if PARENT != None:
123+
send_note(text=update.message.text, parent=PARENT)
124+
else:
125+
update.message.reply_text("Need some extra details...",
126+
reply_markup=main_menu_keyboard())
127+
return FIRST
128+
129+
def search_parent():
130+
global HOME_DIR, REMNOT_API, USER_ID
131+
name = HOME_DIR
132+
url_get_by_name = "https://api.remnote.io/api/v0/get_by_name"
133+
search={
134+
"apiKey": REMNOT_API,
135+
"userId":USER_ID,
136+
"name":name}
137+
res = requests.post(url_get_by_name, data=search)
138+
if res.json()['found'] == True:
139+
return res.json()['_id']
140+
else:
141+
url_post="https://api.remnote.io/api/v0/create"
142+
par={
143+
"apiKey": REMNOT_API,
144+
"userId":USER_ID,
145+
"text": name ,
146+
"isDocument" : True
147+
}
148+
created_rem = requests.post(url_post, data=par)
149+
return created_rem.json()['remId']
150+
151+
# First menu hangling
152+
def daily_docs(update, context):
153+
"""Echo the user message."""
154+
global LINK, DOCUMENT, NOTE, NOTE_ID
155+
query = update.callback_query
156+
query.answer()
157+
NOTE_ID = send_note(text=NOTE, link=LINK)
158+
query.edit_message_text(text = first_menu_message(),
159+
reply_markup=first_menu_keyboard())
160+
return SECOND
161+
162+
163+
def separate_dir(update, context):
164+
"""Echo the user message."""
165+
global LINK, DOCUMENT, NOTE, NOTE_ID, today
166+
query = update.callback_query
167+
query.answer()
168+
NOTE_ID = send_note(text=NOTE + "#[["+today+"]]", link=LINK, parent=search_parent())
169+
query.edit_message_text(text = first_menu_message(),
170+
reply_markup=first_menu_keyboard())
171+
return SECOND
172+
173+
174+
# Second menu hangling
175+
def update_rem(update, context):
176+
"""Echo the user message."""
177+
global PARENT, NOTE_ID
178+
PARENT = NOTE_ID
179+
query = update.callback_query
180+
query.answer()
181+
query.edit_message_text("When you done adding notes, please type /stop command")
182+
return ConversationHandler.END
183+
184+
def stop_conv(update, context):
185+
"""Echo the user message."""
186+
query = update.callback_query
187+
query.answer()
188+
query.edit_message_text("Sure.")
189+
return ConversationHandler.END
190+
191+
# Stop notes adding
192+
def stop(update, context):
193+
"""Echo the user message."""
194+
global PARENT
195+
PARENT=None
196+
DOCUMENT = False
197+
LINK = False
198+
199+
# Menus
200+
def main_menu(update, context):
201+
query = update.callback_query
202+
query.answer()
203+
query.edit_message_text(main_menu_message(),
204+
reply_markup=main_menu_keyboard())
205+
return FIRST
206+
207+
208+
def main_menu_keyboard():
209+
keyboard = [[InlineKeyboardButton('Save to daily notes', callback_data='daily_docs')],
210+
[InlineKeyboardButton('Save to separate dir', callback_data='separate_dir')]]
211+
return InlineKeyboardMarkup(keyboard)
212+
213+
def first_menu_keyboard():
214+
keyboard = [[InlineKeyboardButton('Yes', callback_data='update_rem')],
215+
[InlineKeyboardButton('No', callback_data='stop')]]
216+
return InlineKeyboardMarkup(keyboard)
217+
218+
def main_menu_message():
219+
return 'Where to save?:'
220+
221+
def first_menu_message():
222+
return 'Add some additional notes? (inside created rem):'
223+
224+
# Error messages
225+
def error(update, context):
226+
"""Log Errors caused by Updates."""
227+
logger.warning('Update "%s" caused error "%s"', update, context.error)
228+
229+
def main():
230+
global PARENT
231+
updater = Updater(TOKEN, use_context=True)
232+
dp = updater.dispatcher
233+
conv_handler = ConversationHandler(
234+
entry_points=[
235+
MessageHandler(Filters.text, start),
236+
MessageHandler(Filters.document, start_doc)
237+
],
238+
states={
239+
FIRST: [
240+
CallbackQueryHandler(main_menu, pattern='main'),
241+
CallbackQueryHandler(daily_docs, pattern='daily_docs'),
242+
CallbackQueryHandler(separate_dir, pattern='separate_dir'),
243+
],
244+
SECOND: [
245+
CallbackQueryHandler(update_rem, pattern="update_rem"),
246+
CallbackQueryHandler(stop_conv, pattern="stop"),
247+
],
248+
},
249+
fallbacks=[CommandHandler('start', start)],
250+
)
251+
dp.add_handler(CommandHandler("stop", stop))
252+
dp.add_handler(conv_handler)
253+
dp.add_error_handler(error)
254+
# updater.start_polling()
255+
updater.start_webhook(listen="0.0.0.0",
256+
port=int(PORT),
257+
url_path=TOKEN)
258+
updater.bot.setWebhook(HEROKU_NAME + TOKEN)
259+
updater.idle()
260+
if __name__ == '__main__':
261+
main()

requirements.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
python-telegram-bot==12.7
2+
bs4
3+
requests

0 commit comments

Comments
 (0)