webinterface.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. __filename__ = "webinterface.py"
  2. __author__ = "Bob Mottram"
  3. __license__ = "AGPL3+"
  4. __version__ = "0.0.1"
  5. __maintainer__ = "Bob Mottram"
  6. __email__ = "bob@freedombone.net"
  7. __status__ = "Production"
  8. import json
  9. from person import personBoxJson
  10. from utils import getNicknameFromActor
  11. from utils import getDomainFromActor
  12. def htmlHeader(css=None,lang='en') -> str:
  13. if not css:
  14. htmlStr= \
  15. '<!DOCTYPE html>\n' \
  16. '<html lang="'+lang+'">\n' \
  17. ' <meta charset="utf-8">\n' \
  18. ' <style>\n' \
  19. ' @import url("epicyon.css");\n'+ \
  20. ' </style>\n' \
  21. ' <body>\n'
  22. else:
  23. htmlStr= \
  24. '<!DOCTYPE html>\n' \
  25. '<html lang="'+lang+'">\n' \
  26. ' <meta charset="utf-8">\n' \
  27. ' <style>\n'+css+'</style>\n' \
  28. ' <body>\n'
  29. return htmlStr
  30. def htmlFooter() -> str:
  31. htmlStr= \
  32. ' </body>\n' \
  33. '</html>\n'
  34. return htmlStr
  35. def htmlProfile(baseDir: str,httpPrefix: str,authorized: bool,ocapAlways: bool,profileJson: {}) -> str:
  36. """Show the profile page as html
  37. """
  38. nickname=profileJson['name']
  39. if not nickname:
  40. return ""
  41. preferredName=profileJson['preferredUsername']
  42. domain,port=getDomainFromActor(profileJson['id'])
  43. if not domain:
  44. return ""
  45. domainFull=domain
  46. if port:
  47. domainFull=domain+':'+str(port)
  48. profileDescription=profileJson['publicKey']['summary']
  49. profileDescription='A test description'
  50. profileStr= \
  51. ' <div class="hero-image">' \
  52. ' <div class="hero-text">' \
  53. ' <img src="'+profileJson['icon']['url']+'" alt="'+nickname+'@'+domainFull+'">' \
  54. ' <h1>'+preferredName+'</h1>' \
  55. ' <p><b>@'+nickname+'@'+domainFull+'</b></p>' \
  56. ' <p>'+profileDescription+'</p>' \
  57. ' </div>' \
  58. '</div>' \
  59. '<div class="container">\n' \
  60. ' <center>' \
  61. ' <a href="'+profileJson['id']+'/outbox?page=true"><button class="button"><span>Posts </span></button></a>' \
  62. ' <a href="'+profileJson['id']+'/following?page=true"><button class="button"><span>Following </span></button></a>' \
  63. ' <a href="'+profileJson['id']+'/followers?page=true"><button class="button"><span>Followers </span></button></a>' \
  64. ' <a href="'+profileJson['id']+'/roles?page=true"><button class="button"><span>Roles </span></button></a>' \
  65. ' <a href="'+profileJson['id']+'/skills?page=true"><button class="button"><span>Skills </span></button></a>' \
  66. ' <a href="'+profileJson['id']+'/shares?page=true"><button class="button"><span>Shares </span></button></a>' \
  67. ' </center>' \
  68. '</div>'
  69. profileStyle= \
  70. 'body, html {' \
  71. ' height: 100%;' \
  72. ' margin: 0;' \
  73. ' font-family: Arial, Helvetica, sans-serif;' \
  74. '}' \
  75. '' \
  76. '.hero-image {' \
  77. ' background-image: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url("'+profileJson['id']+'/image.png");' \
  78. ' height: 50%;' \
  79. ' background-position: center;' \
  80. ' background-repeat: no-repeat;' \
  81. ' background-size: cover;' \
  82. ' position: relative;' \
  83. '}' \
  84. '' \
  85. '.hero-text {' \
  86. ' text-align: center;' \
  87. ' position: absolute;' \
  88. ' top: 50%;' \
  89. ' left: 50%;' \
  90. ' transform: translate(-50%, -50%);' \
  91. ' color: white;' \
  92. '}' \
  93. '' \
  94. '.hero-text img {' \
  95. ' border-radius: 10%;' \
  96. ' width: 50%;' \
  97. '}' \
  98. '' \
  99. '.hero-text button {' \
  100. ' border: none;' \
  101. ' outline: 0;' \
  102. ' display: inline-block;' \
  103. ' padding: 10px 25px;' \
  104. ' color: black;' \
  105. ' background-color: #ddd;' \
  106. ' text-align: center;' \
  107. ' cursor: pointer;' \
  108. '}' \
  109. '' \
  110. '.hero-text button:hover {' \
  111. ' background-color: #555;' \
  112. ' color: white;' \
  113. '}' \
  114. '' \
  115. '.button {' \
  116. ' border-radius: 4px;' \
  117. ' background-color: #999;' \
  118. ' border: none;' \
  119. ' color: #FFFFFF;' \
  120. ' text-align: center;' \
  121. ' font-size: 18px;' \
  122. ' padding: 10px;' \
  123. ' width: 20%;' \
  124. ' max-width: 200px;' \
  125. ' min-width: 100px;' \
  126. ' transition: all 0.5s;' \
  127. ' cursor: pointer;' \
  128. ' margin: 5px;' \
  129. '}' \
  130. '' \
  131. '.button span {' \
  132. ' cursor: pointer;' \
  133. ' display: inline-block;' \
  134. ' position: relative;' \
  135. ' transition: 0.5s;' \
  136. '}' \
  137. '' \
  138. '.button span:after {' \
  139. " content: '\\00bb';" \
  140. ' position: absolute;' \
  141. ' opacity: 0;' \
  142. ' top: 0;' \
  143. ' right: -20px;' \
  144. ' transition: 0.5s;' \
  145. '}' \
  146. '' \
  147. '.button:hover span {' \
  148. ' padding-right: 25px;' \
  149. '}' \
  150. '' \
  151. '.button:hover span:after {' \
  152. ' opacity: 1;' \
  153. ' right: 0;' \
  154. '}' \
  155. '.container {' \
  156. ' border: 2px solid #dedede;' \
  157. ' background-color: #f1f1f1;' \
  158. ' border-radius: 5px;' \
  159. ' padding: 10px;' \
  160. ' margin: 10px 0;' \
  161. '}' \
  162. '' \
  163. '.container {' \
  164. ' border: 2px solid #dedede;' \
  165. ' background-color: #f1f1f1;' \
  166. ' border-radius: 5px;' \
  167. ' padding: 10px;' \
  168. ' margin: 10px 0;' \
  169. '}' \
  170. '' \
  171. '.darker {' \
  172. ' border-color: #ccc;' \
  173. ' background-color: #ddd;' \
  174. '}' \
  175. '' \
  176. '.container::after {' \
  177. ' content: "";' \
  178. ' clear: both;' \
  179. ' display: table;' \
  180. '}' \
  181. '' \
  182. '.container img {' \
  183. ' float: left;' \
  184. ' max-width: 60px;' \
  185. ' width: 100%;' \
  186. ' margin-right: 20px;' \
  187. ' border-radius: 10%;' \
  188. '}' \
  189. '' \
  190. '.container img.attachment {' \
  191. ' max-width: 100%;' \
  192. ' margin-left: 25%;' \
  193. ' width: 50%;' \
  194. ' border-radius: 10%;' \
  195. '}' \
  196. '.container img.right {' \
  197. ' float: right;' \
  198. ' margin-left: 20px;' \
  199. ' margin-right:0;' \
  200. '}' \
  201. '' \
  202. '.time-right {' \
  203. ' float: right;' \
  204. ' color: #aaa;' \
  205. '}' \
  206. '' \
  207. '.time-left {' \
  208. ' float: left;' \
  209. ' color: #999;' \
  210. '}' \
  211. '' \
  212. '.post-title {' \
  213. ' margin-top: 0px;' \
  214. ' color: #999;' \
  215. '}'
  216. # show some posts
  217. outboxFeed=personBoxJson(baseDir,domain, \
  218. port,'/users/'+nickname+'/outbox?page=1', \
  219. httpPrefix, \
  220. 4, 'outbox', \
  221. authorized, \
  222. ocapAlways)
  223. for item in outboxFeed['orderedItems']:
  224. if item['type']=='Create':
  225. profileStr+=individualPostAsHtml(item)
  226. profileStr=htmlHeader(profileStyle)+profileStr+htmlFooter()
  227. return profileStr
  228. def htmlFollowing(followingJson: {}) -> str:
  229. """Show the following collection as html
  230. """
  231. return htmlHeader()+"<h1>Following collection</h1>"+htmlFooter()
  232. def htmlFollowers(followersJson: {}) -> str:
  233. """Show the followers collection as html
  234. """
  235. return htmlHeader()+"<h1>Followers collection</h1>"+htmlFooter()
  236. def individualPostAsHtml(postJsonObject: {}) -> str:
  237. avatarPosition=''
  238. containerClass='container'
  239. timeClass='time-right'
  240. nickname=getNicknameFromActor(postJsonObject['actor'])
  241. domain,port=getDomainFromActor(postJsonObject['actor'])
  242. titleStr='@'+nickname+'@'+domain
  243. if postJsonObject['object']['inReplyTo']:
  244. containerClass='container darker'
  245. avatarPosition=' class="right"'
  246. timeClass='time-left'
  247. if '/statuses/' in postJsonObject['object']['inReplyTo']:
  248. replyNickname=getNicknameFromActor(postJsonObject['object']['inReplyTo'])
  249. replyDomain,replyPort=getDomainFromActor(postJsonObject['object']['inReplyTo'])
  250. if replyNickname and replyDomain:
  251. titleStr+=' <i>replying to</i> <a href="'+postJsonObject['object']['inReplyTo']+'">@'+replyNickname+'@'+replyDomain+'</a>'
  252. else:
  253. titleStr+=' <i>replying to</i> '+postJsonObject['object']['inReplyTo']
  254. attachmentStr=''
  255. if postJsonObject['object']['attachment']:
  256. if isinstance(postJsonObject['object']['attachment'], list):
  257. attachmentCtr=0
  258. for attach in postJsonObject['object']['attachment']:
  259. if attach.get('mediaType') and attach.get('url'):
  260. mediaType=attach['mediaType']
  261. imageDescription=''
  262. if attach.get('name'):
  263. imageDescription=attach['name']
  264. if mediaType=='image/png' or \
  265. mediaType=='image/jpeg' or \
  266. mediaType=='image/gif':
  267. if attach['url'].endswith('.png') or \
  268. attach['url'].endswith('.jpg') or \
  269. attach['url'].endswith('.jpeg') or \
  270. attach['url'].endswith('.gif'):
  271. if attachmentCtr>0:
  272. attachmentStr+='<br>'
  273. attachmentStr+= \
  274. '<a href="'+attach['url']+'">' \
  275. '<img src="'+attach['url']+'" alt="'+imageDescription+'" title="'+imageDescription+'" class="attachment"></a>\n'
  276. attachmentCtr+=1
  277. return \
  278. '<div class="'+containerClass+'">\n' \
  279. '<a href="'+postJsonObject['actor']+'">' \
  280. '<img src="'+postJsonObject['actor']+'/avatar.png" alt="Avatar"'+avatarPosition+'></a>\n'+ \
  281. '<p class="post-title">'+titleStr+'</p>'+ \
  282. postJsonObject['object']['content']+'\n'+ \
  283. attachmentStr+ \
  284. '<span class="'+timeClass+'">'+postJsonObject['object']['published']+'</span>\n'+ \
  285. '</div>\n'
  286. def htmlTimeline(timelineJson: {}) -> str:
  287. """Show the timeline as html
  288. """
  289. if not timelineJson.get('orderedItems'):
  290. return ""
  291. tlStr=htmlHeader()
  292. for item in timelineJson['orderedItems']:
  293. if item['type']=='Create':
  294. tlStr+=individualPostAsHtml(item)
  295. tlStr+=htmlFooter()
  296. return tlStr
  297. def htmlInbox(inboxJson: {}) -> str:
  298. """Show the inbox as html
  299. """
  300. return htmlTimeline(inboxJson)
  301. def htmlOutbox(outboxJson: {}) -> str:
  302. """Show the Outbox as html
  303. """
  304. return htmlTimeline(outboxJson)
  305. def htmlIndividualPost(postJsonObject: {}) -> str:
  306. """Show an individual post as html
  307. """
  308. return htmlHeader()+ \
  309. individualPostAsHtml(postJsonObject)+ \
  310. htmlFooter()
  311. def htmlPostReplies(postJsonObject: {}) -> str:
  312. """Show the replies to an individual post as html
  313. """
  314. return htmlHeader()+"<h1>Replies</h1>"+htmlFooter()