mirror of
https://github.com/ComradCollective/Comrad
synced 2024-11-09 13:10:44 +00:00
385 lines
12 KiB
Python
385 lines
12 KiB
Python
## CONFIG
|
|
# change this to your external ip address for your server
|
|
#(needs to be external to allow tor routing)
|
|
SERVER_ADDR = '128.232.229.63:5555'
|
|
DEFAULT_SCREEN='profile'
|
|
HORIZONTAL = False
|
|
WINDOW_SIZE = (1136,640) if HORIZONTAL else (640,1136)
|
|
|
|
import asyncio
|
|
import os
|
|
os.environ['KIVY_EVENTLOOP'] = 'async'
|
|
# loop = asyncio.get_event_loop()
|
|
# loop.set_debug(True)
|
|
|
|
# imports
|
|
from kivy.uix.screenmanager import Screen,ScreenManager
|
|
from kivymd.app import MDApp
|
|
from kivymd.uix.button import MDFillRoundFlatButton, MDIconButton
|
|
from kivymd.uix.toolbar import MDToolbar
|
|
from kivymd.uix.screen import MDScreen
|
|
from kivy.lang import Builder
|
|
from kivy.uix.boxlayout import BoxLayout
|
|
from kivymd.theming import ThemeManager
|
|
from kivy.properties import ObjectProperty,ListProperty
|
|
import time,os
|
|
from collections import OrderedDict
|
|
from functools import partial
|
|
from kivy.uix.screenmanager import NoTransition
|
|
from kivymd.uix.label import MDLabel
|
|
from kivy.uix.widget import Widget
|
|
from kivymd.uix.list import OneLineListItem
|
|
from kivymd.uix.card import MDCard, MDSeparator
|
|
from kivymd.uix.boxlayout import MDBoxLayout
|
|
from kivy.uix.gridlayout import GridLayout
|
|
from kivy.metrics import dp
|
|
from kivy.properties import NumericProperty
|
|
from kivymd.uix.list import * #MDList, ILeftBody, IRightBody, ThreeLineAvatarListItem, TwoLineAvatarListItem, BaseListItem, ImageLeftWidget
|
|
from kivy.uix.image import Image, AsyncImage
|
|
import requests,json
|
|
from kivy.storage.jsonstore import JsonStore
|
|
from kivy.core.window import Window
|
|
from kivy.core.text import LabelBase
|
|
import shutil,sys
|
|
from kivy.uix.image import Image
|
|
import sys
|
|
sys.path.append("..") # Adds higher directory to python modules path.
|
|
from p2p import p2p,crypto,api
|
|
from kivy.event import EventDispatcher
|
|
import threading,asyncio
|
|
|
|
Window.size = WINDOW_SIZE
|
|
|
|
# with open('log.txt','w') as of:
|
|
# of.write('### LOG ###\n')
|
|
|
|
import logging
|
|
handler = logging.StreamHandler()
|
|
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
handler.setFormatter(formatter)
|
|
logger = logging.getLogger('app')
|
|
logger.addHandler(handler)
|
|
logger.setLevel(logging.DEBUG)
|
|
|
|
def log(*args):
|
|
# with open('log.txt','a+') as of:
|
|
# of.write(' '.join([str(x) for x in args])+'\n')
|
|
line = ' '.join(str(x) for x in args)
|
|
logger.debug(line)
|
|
|
|
class MyLayout(MDBoxLayout):
|
|
scr_mngr = ObjectProperty(None)
|
|
post_id = ObjectProperty()
|
|
|
|
def change_screen(self, screen, *args):
|
|
self.scr_mngr.current = screen
|
|
|
|
def view_post(self,post_id):
|
|
self.post_id=post_id
|
|
self.change_screen('view')
|
|
|
|
|
|
class MyBoxLayout(MDBoxLayout): pass
|
|
class MyLabel(MDLabel): pass
|
|
|
|
|
|
|
|
|
|
|
|
def get_tor_proxy_session():
|
|
session = requests.session()
|
|
# Tor uses the 9050 port as the default socks port
|
|
session.proxies = {'http': 'socks5://127.0.0.1:9150',
|
|
'https': 'socks5://127.0.0.1:9150'}
|
|
return session
|
|
|
|
def get_async_tor_proxy_session():
|
|
from requests_futures.sessions import FuturesSession
|
|
session = FuturesSession()
|
|
# Tor uses the 9050 port as the default socks port
|
|
session.proxies = {'http': 'socks5://127.0.0.1:9150',
|
|
'https': 'socks5://127.0.0.1:9150'}
|
|
return session
|
|
|
|
|
|
def get_tor_python_session():
|
|
from torpy.http.requests import TorRequests
|
|
with TorRequests() as tor_requests:
|
|
with tor_requests.get_session() as s:
|
|
return s
|
|
|
|
def draw_background(widget, img_fn='assets/bg.png'):
|
|
from kivy.core.image import Image as CoreImage
|
|
from kivy.graphics import Color, Rectangle
|
|
widget.canvas.before.clear()
|
|
with widget.canvas.before:
|
|
Color(.4, .4, .4, 1)
|
|
texture = CoreImage(img_fn).texture
|
|
texture.wrap = 'repeat'
|
|
nx = float(widget.width) / texture.width
|
|
ny = float(widget.height) / texture.height
|
|
Rectangle(pos=widget.pos, size=widget.size, texture=texture,
|
|
tex_coords=(0, 0, nx, 0, nx, ny, 0, ny))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### LOOPER
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MainApp(MDApp):
|
|
title = 'Komrade'
|
|
logged_in=False
|
|
store = JsonStore('komrade.json')
|
|
login_expiry = 60 * 60 * 24 * 7 # once a week
|
|
texture = ObjectProperty()
|
|
|
|
# def connect(self):
|
|
# # connect to kad?
|
|
# self.node = p2p.connect()
|
|
|
|
def __init__(self, **kwargs):
|
|
super().__init__(**kwargs)
|
|
self.event_loop_worker = None
|
|
self.loop=asyncio.get_event_loop()
|
|
|
|
|
|
|
|
|
|
|
|
def get_session(self):
|
|
# return get_async_tor_proxy_session()
|
|
return get_tor_proxy_session()
|
|
#return get_tor_python_session()
|
|
|
|
def get_username(self):
|
|
if hasattr(self,'username'): return self.username
|
|
self.load_store()
|
|
if hasattr(self,'username'): return self.username
|
|
return ''
|
|
|
|
def build(self):
|
|
# bind bg texture
|
|
# self.texture = Image(source='assets/bg.png').texture
|
|
# self.texture.wrap = 'clamp_to_edge'
|
|
# self.texture.uvsize = (-2, -2)
|
|
# self.start_event_loop_thread()
|
|
|
|
# with open('log.txt','w') as of: of.write('## LOG ##\n')
|
|
self.load_store()
|
|
|
|
# self.boot_kad()
|
|
self.api = api.Api(app_storage=self.store)
|
|
|
|
self.username=''
|
|
# bind
|
|
global app,root
|
|
app = self
|
|
#self.username = self.store.get('userd').get('username')
|
|
|
|
self.root = root = Builder.load_file('root.kv')
|
|
draw_background(self.root)
|
|
|
|
# edit logo
|
|
logo=root.ids.toolbar.ids.label_title
|
|
logo.font_name='assets/Strengthen.ttf'
|
|
logo.font_size='58dp'
|
|
logo.pos_hint={'center_y':0.43}
|
|
# icons
|
|
icons=root.ids.toolbar.ids.right_actions.children
|
|
for icon in icons:
|
|
#log(dir(icon))
|
|
#icon.icon='android' #user_font_size='200sp'
|
|
icon.font_size='58dp'
|
|
icon.user_font_size='58dp'
|
|
icon.width='58dp'
|
|
icon.size_hint=(None,None)
|
|
icon.height='58dp'
|
|
|
|
if not self.is_logged_in():
|
|
self.root.change_screen('login')
|
|
#log(self.username)
|
|
else:
|
|
# self.root.post_id=190
|
|
self.root.change_screen(DEFAULT_SCREEN)
|
|
return self.root
|
|
|
|
|
|
# ## LOOP
|
|
# def start_event_loop_thread(self):
|
|
# """Start the asyncio event loop thread. Bound to the top button."""
|
|
# if self.event_loop_worker is not None:
|
|
# return
|
|
# #self.root.ids.btn.text = ("Running the asyncio EventLoop now...\n\n\n\n"
|
|
# # "Now enter a few words below.")
|
|
# self.event_loop_worker = worker = EventLoopWorker()
|
|
# #pulse_listener_label = self.root.ids.pulse_listener
|
|
|
|
# #def display_on_pulse(instance, text):
|
|
# # pulse_listener_label.text = text
|
|
|
|
# # make the label react to the worker's `on_pulse` event:
|
|
# #worker.bind(on_pulse=display_on_pulse)
|
|
# worker.start()
|
|
|
|
# def submit_pulse_text(self, text):
|
|
# """Send the TextInput string over to the asyncio event loop worker."""
|
|
# worker = self.event_loop_worker
|
|
# if worker is not None:
|
|
# loop = self.event_loop_worker.loop
|
|
# # use the thread safe variant to run it on the asyncio event loop:
|
|
# loop.call_soon_threadsafe(worker.set_pulse_text, text)
|
|
|
|
|
|
|
|
def load_store(self):
|
|
if not self.store.exists('user'): return
|
|
userd=self.store.get('user')
|
|
if not userd: userd={}
|
|
self.logged_in_when = userd.get('logged_in_when')
|
|
self.username = userd.get('username','')
|
|
|
|
def is_logged_in(self,just_check_timestamp=True, use_caching=True):
|
|
# self.username='root'
|
|
# return True
|
|
if self.logged_in: return True
|
|
if not use_caching: return False
|
|
|
|
###
|
|
if not self.store.exists('user'): return False
|
|
userd=self.store.get('user')
|
|
if not userd: userd={}
|
|
if userd.get('logged_in'):
|
|
un=userd.get('username')
|
|
timestamp=userd.get('logged_in_when')
|
|
|
|
# just a time check
|
|
if timestamp and just_check_timestamp:
|
|
if time.time() - timestamp < self.login_expiry:
|
|
self.logged_in=True
|
|
#self.username=un
|
|
return True
|
|
|
|
return False
|
|
|
|
def save_login(self,un):
|
|
self.logged_in=True
|
|
self.username=un
|
|
# self.store.put('username',un)
|
|
# self.store.put('user',username=un,logged_in=True,logged_in_when=time.time())
|
|
self.root.change_screen('feed')
|
|
|
|
|
|
def login(self,un=None,pw=None):
|
|
dat = self.api.login(un,pw)
|
|
log(dat)
|
|
if 'success' in dat:
|
|
self.save_login(un)
|
|
elif 'error' in dat:
|
|
self.root.ids.login_screen.login_status.text=dat['error']
|
|
return False
|
|
|
|
def register(self,un,pw):
|
|
dat = self.api.register(un,pw)
|
|
if 'success' in dat:
|
|
self.save_login(un)
|
|
return True
|
|
elif 'error' in dat:
|
|
self.root.ids.login_screen.login_status.text=dat['error']
|
|
return False
|
|
|
|
def upload(self,filename,file_id=None):
|
|
log('uploading filename:',filename)
|
|
rdata=self.api.upload(filename,file_id=file_id)
|
|
log('upload result:',rdata)
|
|
if rdata is not None:
|
|
rdata['success']='File uploaded'
|
|
return rdata
|
|
return {'error':'Upload failed'}
|
|
|
|
def download(self,file_id,output_fn=None):
|
|
log('downloading:',file_id)
|
|
file_dat = self.api.download(file_id)
|
|
if not output_fn:
|
|
file_id=file_dat['id']
|
|
file_ext=file_dat['ext']
|
|
output_fn=os.path.join('cache',file_id[:3]+'/'+file_id[3:]+'.'+file_ext)
|
|
|
|
output_dir=os.path.dirname(output_fn)
|
|
if not os.path.exists(output_dir): os.makedirs(output_dir)
|
|
|
|
with open(output_fn,'wb') as of:
|
|
for data_piece in file_dat['parts_data']:
|
|
if data_piece is not None:
|
|
of.write(data_piece)
|
|
|
|
def post(self, content='', file_id=None, file_ext=None, anonymous=False):
|
|
#timestamp=time.time()
|
|
jsond={}
|
|
#jsond['timestamp']=
|
|
if content: jsond['content']=str(content)
|
|
if file_id: jsond['file_id']=str(file_id)
|
|
if file_ext: jsond['file_ext']=str(file_ext)
|
|
if not anonymous and self.username:
|
|
jsond['author']=self.username
|
|
|
|
log('posting:',jsond)
|
|
res=self.api.post(jsond)
|
|
if 'success' in res:
|
|
self.root.change_screen('feed')
|
|
return {'post_id':res['post_id']}
|
|
|
|
|
|
|
|
|
|
|
|
def get_post(self,post_id):
|
|
return self.api.get_post(post_id)
|
|
|
|
def get_posts(self):
|
|
return self.api.get_posts()
|
|
|
|
def get_my_posts(self):
|
|
return self.api.get_posts('/author/'+self.username)
|
|
|
|
|
|
def get_image(self, img_src):
|
|
# is there an image?
|
|
if not img_src: return
|
|
# is it cached?
|
|
ofn_image = os.path.join('cache','img',img_src)
|
|
if not os.path.exists(ofn_image):
|
|
# create dir?
|
|
ofn_image_dir = os.path.split(ofn_image)[0]
|
|
if not os.path.exists(ofn_image_dir): os.makedirs(ofn_image_dir)
|
|
log('getting image!')
|
|
with self.get_session() as sess:
|
|
with sess.get(self.api+'/download/'+img_src,stream=True) as r:
|
|
with open(ofn_image,'wb') as of:
|
|
shutil.copyfileobj(r.raw, of)
|
|
return ofn_image
|
|
|
|
|
|
|
|
def main():
|
|
App = MainApp()
|
|
App.run()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
# loop = asyncio.get_event_loop()
|
|
# asyncio.set_event_loop(loop)
|
|
# loop.run_until_complete(main())
|
|
# loop.close()
|
|
main()
|