epicyon.py 67 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826
  1. __filename__ = "epicyon.py"
  2. __author__ = "Bob Mottram"
  3. __license__ = "AGPL3+"
  4. __version__ = "1.1.0"
  5. __maintainer__ = "Bob Mottram"
  6. __email__ = "bob@freedombone.net"
  7. __status__ = "Production"
  8. from person import createPerson
  9. from person import createGroup
  10. from person import setProfileImage
  11. from person import removeAccount
  12. from person import activateAccount
  13. from person import deactivateAccount
  14. from skills import setSkillLevel
  15. from roles import setRole
  16. from webfinger import webfingerHandle
  17. from posts import getPublicPostDomains
  18. from posts import sendBlockViaServer
  19. from posts import sendUndoBlockViaServer
  20. from posts import createPublicPost
  21. from posts import deleteAllPosts
  22. from posts import archivePosts
  23. from posts import sendPostViaServer
  24. from posts import getPublicPostsOfPerson
  25. from posts import getUserUrl
  26. from session import createSession
  27. from session import getJson
  28. from filters import addFilter
  29. from filters import removeFilter
  30. import os
  31. import shutil
  32. import sys
  33. import time
  34. from pprint import pprint
  35. from daemon import runDaemon
  36. from follow import clearFollows
  37. from follow import followerOfPerson
  38. from follow import sendFollowRequestViaServer
  39. from follow import sendUnfollowRequestViaServer
  40. from tests import testPostMessageBetweenServers
  41. from tests import testFollowBetweenServers
  42. from tests import testClientToServer
  43. from tests import runAllTests
  44. from config import setConfigParam
  45. from config import getConfigParam
  46. from auth import storeBasicCredentials
  47. from auth import createPassword
  48. from utils import getDomainFromActor
  49. from utils import getNicknameFromActor
  50. from utils import followPerson
  51. from utils import validNickname
  52. from utils import getProtocolPrefixes
  53. from media import archiveMedia
  54. from media import getAttachmentMediaType
  55. from delete import sendDeleteViaServer
  56. from like import sendLikeViaServer
  57. from like import sendUndoLikeViaServer
  58. from roles import sendRoleViaServer
  59. from skills import sendSkillViaServer
  60. from availability import setAvailability
  61. from availability import sendAvailabilityViaServer
  62. from manualapprove import manualDenyFollowRequest
  63. from manualapprove import manualApproveFollowRequest
  64. from shares import sendShareViaServer
  65. from shares import sendUndoShareViaServer
  66. from shares import addShare
  67. from theme import setTheme
  68. from announce import sendAnnounceViaServer
  69. from socnet import instancesGraph
  70. import argparse
  71. def str2bool(v):
  72. if isinstance(v, bool):
  73. return v
  74. if v.lower() in ('yes', 'true', 't', 'y', '1'):
  75. return True
  76. elif v.lower() in ('no', 'false', 'f', 'n', '0'):
  77. return False
  78. else:
  79. raise argparse.ArgumentTypeError('Boolean value expected.')
  80. parser = argparse.ArgumentParser(description='ActivityPub Server')
  81. parser.add_argument('-n', '--nickname', dest='nickname', type=str,
  82. default=None,
  83. help='Nickname of the account to use')
  84. parser.add_argument('--fol', '--follow', dest='follow', type=str,
  85. default=None,
  86. help='Handle of account to follow. eg. nickname@domain')
  87. parser.add_argument('--unfol', '--unfollow', dest='unfollow', type=str,
  88. default=None,
  89. help='Handle of account stop following. ' +
  90. 'eg. nickname@domain')
  91. parser.add_argument('-d', '--domain', dest='domain', type=str,
  92. default=None,
  93. help='Domain name of the server')
  94. parser.add_argument('-o', '--onion', dest='onion', type=str,
  95. default=None,
  96. help='Onion domain name of the server if ' +
  97. 'primarily on clearnet')
  98. parser.add_argument('--i2pDomain', dest='i2pDomain', type=str,
  99. default=None,
  100. help='i2p domain name of the server if ' +
  101. 'primarily on clearnet')
  102. parser.add_argument('-p', '--port', dest='port', type=int,
  103. default=None,
  104. help='Port number to run on')
  105. parser.add_argument('--postcache', dest='maxRecentPosts', type=int,
  106. default=100,
  107. help='The maximum number of recent posts to store in RAM')
  108. parser.add_argument('--proxy', dest='proxyPort', type=int, default=None,
  109. help='Proxy port number to run on')
  110. parser.add_argument('--path', dest='baseDir',
  111. type=str, default=os.getcwd(),
  112. help='Directory in which to store posts')
  113. parser.add_argument('--ytdomain', dest='YTReplacementDomain',
  114. type=str, default=None,
  115. help='Domain used to replace youtube.com')
  116. parser.add_argument('--language', dest='language',
  117. type=str, default=None,
  118. help='Language code, eg. en/fr/de/es')
  119. parser.add_argument('-a', '--addaccount', dest='addaccount',
  120. type=str, default=None,
  121. help='Adds a new account')
  122. parser.add_argument('-g', '--addgroup', dest='addgroup',
  123. type=str, default=None,
  124. help='Adds a new group')
  125. parser.add_argument('--activate', dest='activate',
  126. type=str, default=None,
  127. help='Activate a previously deactivated account')
  128. parser.add_argument('--deactivate', dest='deactivate',
  129. type=str, default=None,
  130. help='Deactivate an account')
  131. parser.add_argument('-r', '--rmaccount', dest='rmaccount',
  132. type=str, default=None,
  133. help='Remove an account')
  134. parser.add_argument('--rmgroup', dest='rmgroup',
  135. type=str, default=None,
  136. help='Remove a group')
  137. parser.add_argument('--pass', '--password', dest='password',
  138. type=str, default=None,
  139. help='Set a password for an account')
  140. parser.add_argument('--chpass', '--changepassword',
  141. nargs='+', dest='changepassword',
  142. help='Change the password for an account')
  143. parser.add_argument('--actor', dest='actor', type=str,
  144. default=None,
  145. help='Show the json actor the given handle')
  146. parser.add_argument('--posts', dest='posts', type=str,
  147. default=None,
  148. help='Show posts for the given handle')
  149. parser.add_argument('--postDomains', dest='postDomains', type=str,
  150. default=None,
  151. help='Show domains referenced in public '
  152. 'posts for the given handle')
  153. parser.add_argument('--socnet', dest='socnet', type=str,
  154. default=None,
  155. help='Show dot diagram for social network '
  156. 'of federated instances')
  157. parser.add_argument('--postsraw', dest='postsraw', type=str,
  158. default=None,
  159. help='Show raw json of posts for the given handle')
  160. parser.add_argument('--json', dest='json', type=str, default=None,
  161. help='Show the json for a given activitypub url')
  162. parser.add_argument('-f', '--federate', nargs='+', dest='federationList',
  163. help='Specify federation list separated by spaces')
  164. parser.add_argument("--noapproval", type=str2bool, nargs='?',
  165. const=True, default=False,
  166. help="Allow followers without approval")
  167. parser.add_argument("--mediainstance", type=str2bool, nargs='?',
  168. const=True, default=False,
  169. help="Media Instance - favor media over text")
  170. parser.add_argument("--blogsinstance", type=str2bool, nargs='?',
  171. const=True, default=False,
  172. help="Blogs Instance - favor blogs over microblogging")
  173. parser.add_argument("--debug", type=str2bool, nargs='?',
  174. const=True, default=False,
  175. help="Show debug messages")
  176. parser.add_argument("--authenticatedFetch", type=str2bool, nargs='?',
  177. const=True, default=False,
  178. help="Enable authentication on GET requests" +
  179. " for json (authenticated fetch)")
  180. parser.add_argument("--instanceOnlySkillsSearch", type=str2bool, nargs='?',
  181. const=True, default=False,
  182. help="Skills searches only return " +
  183. "results from this instance")
  184. parser.add_argument("--http", type=str2bool, nargs='?',
  185. const=True, default=False,
  186. help="Use http only")
  187. parser.add_argument("--gnunet", type=str2bool, nargs='?',
  188. const=True, default=False,
  189. help="Use gnunet protocol only")
  190. parser.add_argument("--dat", type=str2bool, nargs='?',
  191. const=True, default=False,
  192. help="Use dat protocol only")
  193. parser.add_argument("--hyper", type=str2bool, nargs='?',
  194. const=True, default=False,
  195. help="Use hypercore protocol only")
  196. parser.add_argument("--i2p", type=str2bool, nargs='?',
  197. const=True, default=False,
  198. help="Use i2p protocol only")
  199. parser.add_argument("--tor", type=str2bool, nargs='?',
  200. const=True, default=False,
  201. help="Route via Tor")
  202. parser.add_argument("--tests", type=str2bool, nargs='?',
  203. const=True, default=False,
  204. help="Run unit tests")
  205. parser.add_argument("--testsnetwork", type=str2bool, nargs='?',
  206. const=True, default=False,
  207. help="Run network unit tests")
  208. parser.add_argument("--testdata", type=str2bool, nargs='?',
  209. const=True, default=False,
  210. help="Generate some data for testing purposes")
  211. parser.add_argument("--ocap", type=str2bool, nargs='?',
  212. const=True, default=False,
  213. help="Always strictly enforce object capabilities")
  214. parser.add_argument("--noreply", type=str2bool, nargs='?',
  215. const=True, default=False,
  216. help="Default capabilities don't allow replies on posts")
  217. parser.add_argument("--nolike", type=str2bool, nargs='?',
  218. const=True, default=False,
  219. help="Default capabilities don't allow " +
  220. "likes/favourites on posts")
  221. parser.add_argument("--nopics", type=str2bool, nargs='?',
  222. const=True, default=False,
  223. help="Default capabilities don't allow attached pictures")
  224. parser.add_argument("--noannounce", "--norepeat", type=str2bool, nargs='?',
  225. const=True, default=False,
  226. help="Default capabilities don't allow announce/repeat")
  227. parser.add_argument("--cw", type=str2bool, nargs='?',
  228. const=True, default=False,
  229. help="Default capabilities don't allow posts " +
  230. "without content warnings")
  231. parser.add_argument('--icon', '--avatar', dest='avatar', type=str,
  232. default=None,
  233. help='Set the avatar filename for an account')
  234. parser.add_argument('--image', '--background', dest='backgroundImage',
  235. type=str, default=None,
  236. help='Set the profile background image for an account')
  237. parser.add_argument('--archive', dest='archive', type=str,
  238. default=None,
  239. help='Archive old files to the given directory')
  240. parser.add_argument('--archiveweeks', dest='archiveWeeks', type=str,
  241. default=None,
  242. help='Specify the number of weeks after which ' +
  243. 'data will be archived')
  244. parser.add_argument('--maxposts', dest='archiveMaxPosts', type=str,
  245. default=None,
  246. help='Maximum number of posts in in/outbox')
  247. parser.add_argument('--message', dest='message', type=str,
  248. default=None,
  249. help='Message content')
  250. parser.add_argument('--delete', dest='delete', type=str,
  251. default=None,
  252. help='Delete a specified post')
  253. parser.add_argument("--allowdeletion", type=str2bool, nargs='?',
  254. const=True, default=False,
  255. help="Do not allow deletions")
  256. parser.add_argument('--repeat', '--announce', dest='announce', type=str,
  257. default=None,
  258. help='Announce/repeat a url')
  259. parser.add_argument('--favorite', '--like', dest='like', type=str,
  260. default=None, help='Like a url')
  261. parser.add_argument('--undolike', '--unlike', dest='undolike', type=str,
  262. default=None, help='Undo a like of a url')
  263. parser.add_argument('--sendto', dest='sendto', type=str,
  264. default=None, help='Address to send a post to')
  265. parser.add_argument('--attach', dest='attach', type=str,
  266. default=None, help='File to attach to a post')
  267. parser.add_argument('--imagedescription', dest='imageDescription', type=str,
  268. default=None, help='Description of an attached image')
  269. parser.add_argument("--blurhash", type=str2bool, nargs='?',
  270. const=True, default=False,
  271. help="Create blurhash for an image")
  272. parser.add_argument('--warning', '--warn', '--cwsubject', '--subject',
  273. dest='subject', type=str, default=None,
  274. help='Subject of content warning')
  275. parser.add_argument('--reply', '--replyto', dest='replyto', type=str,
  276. default=None, help='Url of post to reply to')
  277. parser.add_argument("--followersonly", type=str2bool, nargs='?',
  278. const=True, default=True,
  279. help="Send to followers only")
  280. parser.add_argument("--followerspending", type=str2bool, nargs='?',
  281. const=True, default=False,
  282. help="Show a list of followers pending")
  283. parser.add_argument('--approve', dest='approve', type=str, default=None,
  284. help='Approve a follow request')
  285. parser.add_argument('--deny', dest='deny', type=str, default=None,
  286. help='Deny a follow request')
  287. parser.add_argument("-c", "--client", type=str2bool, nargs='?',
  288. const=True, default=False,
  289. help="Use as an ActivityPub client")
  290. parser.add_argument('--maxreplies', dest='maxReplies', type=int, default=64,
  291. help='Maximum number of replies to a post')
  292. parser.add_argument('--maxMentions', '--hellthread', dest='maxMentions',
  293. type=int, default=10,
  294. help='Maximum number of mentions within a post')
  295. parser.add_argument('--maxEmoji', '--maxemoji', dest='maxEmoji',
  296. type=int, default=10,
  297. help='Maximum number of emoji within a post')
  298. parser.add_argument('--role', dest='role', type=str, default=None,
  299. help='Set a role for a person')
  300. parser.add_argument('--organization', '--project', dest='project',
  301. type=str, default=None,
  302. help='Set a project for a person')
  303. parser.add_argument('--skill', dest='skill', type=str, default=None,
  304. help='Set a skill for a person')
  305. parser.add_argument('--level', dest='skillLevelPercent', type=int,
  306. default=None,
  307. help='Set a skill level for a person as a ' +
  308. 'percentage, or zero to remove')
  309. parser.add_argument('--status', '--availability', dest='availability',
  310. type=str, default=None,
  311. help='Set an availability status')
  312. parser.add_argument('--block', dest='block', type=str, default=None,
  313. help='Block a particular address')
  314. parser.add_argument('--unblock', dest='unblock', type=str, default=None,
  315. help='Remove a block on a particular address')
  316. parser.add_argument('--delegate', dest='delegate', type=str, default=None,
  317. help='Address of an account to delegate a role to')
  318. parser.add_argument('--undodelegate', '--undelegate', dest='undelegate',
  319. type=str, default=None,
  320. help='Removes a delegated role for the given address')
  321. parser.add_argument('--filter', dest='filterStr', type=str, default=None,
  322. help='Adds a word or phrase which if present will ' +
  323. 'cause a message to be ignored')
  324. parser.add_argument('--unfilter', dest='unfilterStr', type=str, default=None,
  325. help='Remove a filter on a particular word or phrase')
  326. parser.add_argument('--domainmax', dest='domainMaxPostsPerDay', type=int,
  327. default=8640,
  328. help='Maximum number of received posts ' +
  329. 'from a domain per day')
  330. parser.add_argument('--accountmax', dest='accountMaxPostsPerDay', type=int,
  331. default=8640,
  332. help='Maximum number of received posts ' +
  333. 'from an account per day')
  334. parser.add_argument('--itemName', dest='itemName', type=str,
  335. default=None,
  336. help='Name of an item being shared')
  337. parser.add_argument('--undoItemName', dest='undoItemName', type=str,
  338. default=None,
  339. help='Name of an shared item to remove')
  340. parser.add_argument('--summary', dest='summary', type=str,
  341. default=None,
  342. help='Description of an item being shared')
  343. parser.add_argument('--itemImage', dest='itemImage', type=str,
  344. default=None,
  345. help='Filename of an image for an item being shared')
  346. parser.add_argument('--itemType', dest='itemType', type=str,
  347. default=None,
  348. help='Type of item being shared')
  349. parser.add_argument('--itemCategory', dest='itemCategory', type=str,
  350. default=None,
  351. help='Category of item being shared')
  352. parser.add_argument('--location', dest='location', type=str, default=None,
  353. help='Location/City of item being shared')
  354. parser.add_argument('--duration', dest='duration', type=str, default=None,
  355. help='Duration for which to share an item')
  356. parser.add_argument('--registration', dest='registration', type=str,
  357. default=None,
  358. help='Whether new registrations are open or closed')
  359. parser.add_argument("--nosharedinbox", type=str2bool, nargs='?',
  360. const=True, default=False,
  361. help='Disable shared inbox')
  362. parser.add_argument('--maxregistrations', dest='maxRegistrations',
  363. type=int, default=None,
  364. help='The maximum number of new registrations')
  365. parser.add_argument("--resetregistrations", type=str2bool, nargs='?',
  366. const=True, default=False,
  367. help="Reset the number of remaining registrations")
  368. args = parser.parse_args()
  369. debug = False
  370. if args.debug:
  371. debug = True
  372. if args.tests:
  373. runAllTests()
  374. sys.exit()
  375. if args.testsnetwork:
  376. print('Network Tests')
  377. testPostMessageBetweenServers()
  378. testFollowBetweenServers()
  379. testClientToServer()
  380. print('All tests succeeded')
  381. sys.exit()
  382. httpPrefix = 'https'
  383. if args.http or args.i2p:
  384. httpPrefix = 'http'
  385. elif args.gnunet:
  386. httpPrefix = 'gnunet'
  387. baseDir = args.baseDir
  388. if baseDir.endswith('/'):
  389. print("--path option should not end with '/'")
  390. sys.exit()
  391. if args.posts:
  392. if '@' not in args.posts:
  393. if '/users/' in args.posts:
  394. postsNickname = getNicknameFromActor(args.posts)
  395. postsDomain, postsPort = getDomainFromActor(args.posts)
  396. args.posts = postsNickname + '@' + postsDomain
  397. if postsPort:
  398. if postsPort != 80 and postsPort != 443:
  399. args.posts += ':' + str(postsPort)
  400. else:
  401. print('Syntax: --posts nickname@domain')
  402. sys.exit()
  403. if not args.http:
  404. args.port = 443
  405. nickname = args.posts.split('@')[0]
  406. domain = args.posts.split('@')[1]
  407. proxyType = None
  408. if args.tor or domain.endswith('.onion'):
  409. proxyType = 'tor'
  410. if domain.endswith('.onion'):
  411. args.port = 80
  412. elif args.i2p or domain.endswith('.i2p'):
  413. proxyType = 'i2p'
  414. if domain.endswith('.i2p'):
  415. args.port = 80
  416. elif args.gnunet:
  417. proxyType = 'gnunet'
  418. getPublicPostsOfPerson(baseDir, nickname, domain, False, True,
  419. proxyType, args.port, httpPrefix, debug,
  420. __version__)
  421. sys.exit()
  422. if args.postDomains:
  423. if '@' not in args.postDomains:
  424. if '/users/' in args.postDomains:
  425. postsNickname = getNicknameFromActor(args.posts)
  426. postsDomain, postsPort = getDomainFromActor(args.posts)
  427. args.postDomains = postsNickname + '@' + postsDomain
  428. if postsPort:
  429. if postsPort != 80 and postsPort != 443:
  430. args.postDomains += ':' + str(postsPort)
  431. else:
  432. print('Syntax: --postDomains nickname@domain')
  433. sys.exit()
  434. if not args.http:
  435. args.port = 443
  436. nickname = args.postDomains.split('@')[0]
  437. domain = args.postDomains.split('@')[1]
  438. proxyType = None
  439. if args.tor or domain.endswith('.onion'):
  440. proxyType = 'tor'
  441. if domain.endswith('.onion'):
  442. args.port = 80
  443. elif args.i2p or domain.endswith('.i2p'):
  444. proxyType = 'i2p'
  445. if domain.endswith('.i2p'):
  446. args.port = 80
  447. elif args.gnunet:
  448. proxyType = 'gnunet'
  449. domainList = []
  450. domainList = getPublicPostDomains(baseDir, nickname, domain,
  451. proxyType, args.port,
  452. httpPrefix, debug,
  453. __version__, domainList)
  454. for postDomain in domainList:
  455. print(postDomain)
  456. sys.exit()
  457. if args.socnet:
  458. if ',' not in args.socnet:
  459. print('Syntax: '
  460. '--socnet nick1@domain1,nick2@domain2,nick3@domain3')
  461. sys.exit()
  462. if not args.http:
  463. args.port = 443
  464. proxyType = 'tor'
  465. dotGraph = instancesGraph(baseDir, args.socnet,
  466. proxyType, args.port,
  467. httpPrefix, debug,
  468. __version__)
  469. try:
  470. with open('socnet.dot', 'w+') as fp:
  471. fp.write(dotGraph)
  472. print('Saved to socnet.dot')
  473. except BaseException:
  474. pass
  475. sys.exit()
  476. if args.postsraw:
  477. if '@' not in args.postsraw:
  478. print('Syntax: --postsraw nickname@domain')
  479. sys.exit()
  480. if not args.http:
  481. args.port = 443
  482. nickname = args.postsraw.split('@')[0]
  483. domain = args.postsraw.split('@')[1]
  484. proxyType = None
  485. if args.tor or domain.endswith('.onion'):
  486. proxyType = 'tor'
  487. elif args.i2p or domain.endswith('.i2p'):
  488. proxyType = 'i2p'
  489. elif args.gnunet:
  490. proxyType = 'gnunet'
  491. getPublicPostsOfPerson(baseDir, nickname, domain, False, False,
  492. proxyType, args.port, httpPrefix, debug,
  493. __version__)
  494. sys.exit()
  495. if args.json:
  496. session = createSession(None)
  497. profileStr = 'https://www.w3.org/ns/activitystreams'
  498. asHeader = {
  499. 'Accept': 'application/ld+json; profile="' + profileStr + '"'
  500. }
  501. testJson = getJson(session, args.json, asHeader, None,
  502. __version__, httpPrefix, None)
  503. pprint(testJson)
  504. sys.exit()
  505. # create cache for actors
  506. if not os.path.isdir(baseDir + '/cache'):
  507. os.mkdir(baseDir + '/cache')
  508. if not os.path.isdir(baseDir + '/cache/actors'):
  509. print('Creating actors cache')
  510. os.mkdir(baseDir + '/cache/actors')
  511. if not os.path.isdir(baseDir + '/cache/announce'):
  512. print('Creating announce cache')
  513. os.mkdir(baseDir + '/cache/announce')
  514. # set the theme in config.json
  515. themeName = getConfigParam(baseDir, 'theme')
  516. if not themeName:
  517. setConfigParam(baseDir, 'theme', 'default')
  518. themeName = 'default'
  519. if not args.mediainstance:
  520. mediaInstance = getConfigParam(baseDir, 'mediaInstance')
  521. if mediaInstance is not None:
  522. args.mediainstance = mediaInstance
  523. if not args.blogsinstance:
  524. blogsInstance = getConfigParam(baseDir, 'blogsInstance')
  525. if blogsInstance is not None:
  526. args.blogsinstance = blogsInstance
  527. # set the instance title in config.json
  528. title = getConfigParam(baseDir, 'instanceTitle')
  529. if not title:
  530. setConfigParam(baseDir, 'instanceTitle', 'Epicyon')
  531. # set the instance description in config.json
  532. descFull = getConfigParam(baseDir, 'instanceDescription')
  533. if not descFull:
  534. setConfigParam(baseDir, 'instanceDescription',
  535. 'Just another ActivityPub server')
  536. # set the short instance description in config.json
  537. descShort = getConfigParam(baseDir, 'instanceDescriptionShort')
  538. if not descShort:
  539. setConfigParam(baseDir, 'instanceDescriptionShort',
  540. 'Just another ActivityPub server')
  541. if args.domain:
  542. domain = args.domain
  543. setConfigParam(baseDir, 'domain', domain)
  544. if args.onion:
  545. if not args.onion.endswith('.onion'):
  546. print(args.onion + ' does not look like an onion domain')
  547. sys.exit()
  548. if '://' in args.onion:
  549. args.onion = args.onion.split('://')[1]
  550. onionDomain = args.onion
  551. setConfigParam(baseDir, 'onion', onionDomain)
  552. i2pDomain = None
  553. if args.i2pDomain:
  554. if not args.i2pDomain.endswith('.i2p'):
  555. print(args.i2pDomain + ' does not look like an i2p domain')
  556. sys.exit()
  557. if '://' in args.i2pDomain:
  558. args.onion = args.onion.split('://')[1]
  559. i2pDomain = args.i2pDomain
  560. setConfigParam(baseDir, 'i2pDomain', i2pDomain)
  561. if not args.language:
  562. languageCode = getConfigParam(baseDir, 'language')
  563. if languageCode:
  564. args.language = languageCode
  565. # maximum number of new registrations
  566. if not args.maxRegistrations:
  567. maxRegistrations = getConfigParam(baseDir, 'maxRegistrations')
  568. if not maxRegistrations:
  569. maxRegistrations = 10
  570. setConfigParam(baseDir, 'maxRegistrations', str(maxRegistrations))
  571. else:
  572. maxRegistrations = int(maxRegistrations)
  573. else:
  574. maxRegistrations = args.maxRegistrations
  575. setConfigParam(baseDir, 'maxRegistrations', str(maxRegistrations))
  576. # if this is the initial run then allow new registrations
  577. if not getConfigParam(baseDir, 'registration'):
  578. setConfigParam(baseDir, 'registration', 'open')
  579. setConfigParam(baseDir, 'maxRegistrations', str(maxRegistrations))
  580. setConfigParam(baseDir, 'registrationsRemaining', str(maxRegistrations))
  581. if args.resetregistrations:
  582. setConfigParam(baseDir, 'registrationsRemaining', str(maxRegistrations))
  583. print('Number of new registrations reset to ' + str(maxRegistrations))
  584. # whether new registrations are open or closed
  585. if args.registration:
  586. if args.registration.lower() == 'open':
  587. registration = getConfigParam(baseDir, 'registration')
  588. if not registration:
  589. setConfigParam(baseDir, 'registrationsRemaining',
  590. str(maxRegistrations))
  591. else:
  592. if registration != 'open':
  593. setConfigParam(baseDir, 'registrationsRemaining',
  594. str(maxRegistrations))
  595. setConfigParam(baseDir, 'registration', 'open')
  596. print('New registrations open')
  597. else:
  598. setConfigParam(baseDir, 'registration', 'closed')
  599. print('New registrations closed')
  600. # unique ID for the instance
  601. instanceId = getConfigParam(baseDir, 'instanceId')
  602. if not instanceId:
  603. instanceId = createPassword(32)
  604. setConfigParam(baseDir, 'instanceId', instanceId)
  605. print('Instance ID: ' + instanceId)
  606. # get domain name from configuration
  607. configDomain = getConfigParam(baseDir, 'domain')
  608. if configDomain:
  609. domain = configDomain
  610. else:
  611. domain = 'localhost'
  612. # get onion domain name from configuration
  613. configOnionDomain = getConfigParam(baseDir, 'onion')
  614. if configOnionDomain:
  615. onionDomain = configOnionDomain
  616. else:
  617. onionDomain = None
  618. # get i2p domain name from configuration
  619. configi2pDomain = getConfigParam(baseDir, 'i2pDomain')
  620. if configi2pDomain:
  621. i2pDomain = configi2pDomain
  622. else:
  623. i2pDomain = None
  624. # get port number from configuration
  625. configPort = getConfigParam(baseDir, 'port')
  626. if configPort:
  627. port = configPort
  628. else:
  629. port = 8085
  630. configProxyPort = getConfigParam(baseDir, 'proxyPort')
  631. if configProxyPort:
  632. proxyPort = configProxyPort
  633. else:
  634. proxyPort = port
  635. nickname = None
  636. if args.nickname:
  637. nickname = nickname
  638. federationList = []
  639. if args.federationList:
  640. if len(args.federationList) == 1:
  641. if not (args.federationList[0].lower() == 'any' or
  642. args.federationList[0].lower() == 'all' or
  643. args.federationList[0].lower() == '*'):
  644. for federationDomain in args.federationList:
  645. if '@' in federationDomain:
  646. print(federationDomain +
  647. ': Federate with domains, not individual accounts')
  648. sys.exit()
  649. federationList = args.federationList.copy()
  650. setConfigParam(baseDir, 'federationList', federationList)
  651. else:
  652. configFederationList = getConfigParam(baseDir, 'federationList')
  653. if configFederationList:
  654. federationList = configFederationList
  655. proxyType = None
  656. if args.tor or domain.endswith('.onion'):
  657. proxyType = 'tor'
  658. elif args.i2p or domain.endswith('.i2p'):
  659. proxyType = 'i2p'
  660. elif args.gnunet:
  661. proxyType = 'gnunet'
  662. if args.approve:
  663. if not args.nickname:
  664. print('Specify a nickname with the --nickname option')
  665. sys.exit()
  666. if '@' not in args.approve:
  667. print('syntax: --approve nick@domain')
  668. sys.exit()
  669. session = createSession(proxyType)
  670. sendThreads = []
  671. postLog = []
  672. cachedWebfingers = {}
  673. personCache = {}
  674. acceptedCaps = []
  675. manualApproveFollowRequest(session, baseDir,
  676. httpPrefix,
  677. args.nickname, domain, port,
  678. args.approve,
  679. federationList,
  680. sendThreads, postLog,
  681. cachedWebfingers, personCache,
  682. acceptedCaps,
  683. debug, __version__)
  684. sys.exit()
  685. if args.deny:
  686. if not args.nickname:
  687. print('Specify a nickname with the --nickname option')
  688. sys.exit()
  689. if '@' not in args.deny:
  690. print('syntax: --deny nick@domain')
  691. sys.exit()
  692. session = createSession(proxyType)
  693. sendThreads = []
  694. postLog = []
  695. cachedWebfingers = {}
  696. personCache = {}
  697. manualDenyFollowRequest(session, baseDir,
  698. httpPrefix,
  699. args.nickname, domain, port,
  700. args.deny,
  701. federationList,
  702. sendThreads, postLog,
  703. cachedWebfingers, personCache,
  704. debug, __version__)
  705. sys.exit()
  706. if args.followerspending:
  707. if not args.nickname:
  708. print('Specify a nickname with the --nickname option')
  709. sys.exit()
  710. accountsDir = baseDir + '/accounts/' + args.nickname + '@' + domain
  711. approveFollowsFilename = accountsDir + '/followrequests.txt'
  712. approveCtr = 0
  713. if os.path.isfile(approveFollowsFilename):
  714. with open(approveFollowsFilename, 'r') as approvefile:
  715. for approve in approvefile:
  716. print(approve.replace('\n', '').replace('\r', ''))
  717. approveCtr += 1
  718. if approveCtr == 0:
  719. print('There are no follow requests pending approval.')
  720. sys.exit()
  721. if args.message:
  722. if not args.nickname:
  723. print('Specify a nickname with the --nickname option')
  724. sys.exit()
  725. if not args.password:
  726. print('Specify a password with the --password option')
  727. sys.exit()
  728. session = createSession(proxyType)
  729. if not args.sendto:
  730. print('Specify an account to sent to: --sendto [nickname@domain]')
  731. sys.exit()
  732. if '@' not in args.sendto and \
  733. not args.sendto.lower().endswith('public') and \
  734. not args.sendto.lower().endswith('followers'):
  735. print('syntax: --sendto [nickname@domain]')
  736. print(' --sendto public')
  737. print(' --sendto followers')
  738. sys.exit()
  739. if '@' in args.sendto:
  740. toNickname = args.sendto.split('@')[0]
  741. toDomain = args.sendto.split('@')[1]
  742. toDomain = toDomain.replace('\n', '').replace('\r', '')
  743. toPort = 443
  744. if ':' in toDomain:
  745. toPort = toDomain.split(':')[1]
  746. toDomain = toDomain.split(':')[0]
  747. else:
  748. if args.sendto.endswith('followers'):
  749. toNickname = None
  750. toDomain = 'followers'
  751. toPort = port
  752. else:
  753. toNickname = None
  754. toDomain = 'public'
  755. toPort = port
  756. # ccUrl=httpPrefix+'://'+domain+'/users/'+nickname+'/followers'
  757. ccUrl = None
  758. sendMessage = args.message
  759. followersOnly = args.followersonly
  760. clientToServer = args.client
  761. attachedImageDescription = args.imageDescription
  762. useBlurhash = args.blurhash
  763. sendThreads = []
  764. postLog = []
  765. personCache = {}
  766. cachedWebfingers = {}
  767. subject = args.subject
  768. attach = args.attach
  769. mediaType = None
  770. if attach:
  771. mediaType = getAttachmentMediaType(attach)
  772. replyTo = args.replyto
  773. followersOnly = False
  774. isArticle = False
  775. print('Sending post to ' + args.sendto)
  776. sendPostViaServer(__version__,
  777. baseDir, session, args.nickname, args.password,
  778. domain, port,
  779. toNickname, toDomain, toPort, ccUrl,
  780. httpPrefix, sendMessage, followersOnly,
  781. attach, mediaType,
  782. attachedImageDescription, useBlurhash,
  783. cachedWebfingers, personCache, isArticle,
  784. args.debug, replyTo, replyTo, subject)
  785. for i in range(10):
  786. # TODO detect send success/fail
  787. time.sleep(1)
  788. sys.exit()
  789. if args.announce:
  790. if not args.nickname:
  791. print('Specify a nickname with the --nickname option')
  792. sys.exit()
  793. if not args.password:
  794. print('Specify a password with the --password option')
  795. sys.exit()
  796. session = createSession(proxyType)
  797. personCache = {}
  798. cachedWebfingers = {}
  799. print('Sending announce/repeat of ' + args.announce)
  800. sendAnnounceViaServer(baseDir, session, args.nickname, args.password,
  801. domain, port,
  802. httpPrefix, args.announce,
  803. cachedWebfingers, personCache,
  804. True, __version__)
  805. for i in range(10):
  806. # TODO detect send success/fail
  807. time.sleep(1)
  808. sys.exit()
  809. if args.itemName:
  810. if not args.password:
  811. print('Specify a password with the --password option')
  812. sys.exit()
  813. if not args.nickname:
  814. print('Specify a nickname with the --nickname option')
  815. sys.exit()
  816. if not args.summary:
  817. print('Specify a description for your shared item ' +
  818. 'with the --summary option')
  819. sys.exit()
  820. if not args.itemType:
  821. print('Specify a type of shared item with the --itemType option')
  822. sys.exit()
  823. if not args.itemCategory:
  824. print('Specify a category of shared item ' +
  825. 'with the --itemCategory option')
  826. sys.exit()
  827. if not args.location:
  828. print('Specify a location or city where theshared ' +
  829. 'item resides with the --location option')
  830. sys.exit()
  831. if not args.duration:
  832. print('Specify a duration to share the object ' +
  833. 'with the --duration option')
  834. sys.exit()
  835. session = createSession(proxyType)
  836. personCache = {}
  837. cachedWebfingers = {}
  838. print('Sending shared item: ' + args.itemName)
  839. sendShareViaServer(baseDir, session,
  840. args.nickname, args.password,
  841. domain, port,
  842. httpPrefix,
  843. args.itemName,
  844. args.summary,
  845. args.itemImage,
  846. args.itemType,
  847. args.itemCategory,
  848. args.location,
  849. args.duration,
  850. cachedWebfingers, personCache,
  851. debug, __version__)
  852. for i in range(10):
  853. # TODO detect send success/fail
  854. time.sleep(1)
  855. sys.exit()
  856. if args.undoItemName:
  857. if not args.password:
  858. print('Specify a password with the --password option')
  859. sys.exit()
  860. if not args.nickname:
  861. print('Specify a nickname with the --nickname option')
  862. sys.exit()
  863. session = createSession(proxyType)
  864. personCache = {}
  865. cachedWebfingers = {}
  866. print('Sending undo of shared item: ' + args.undoItemName)
  867. sendUndoShareViaServer(session,
  868. args.nickname, args.password,
  869. domain, port,
  870. httpPrefix,
  871. args.undoItemName,
  872. cachedWebfingers, personCache,
  873. debug, __version__)
  874. for i in range(10):
  875. # TODO detect send success/fail
  876. time.sleep(1)
  877. sys.exit()
  878. if args.like:
  879. if not args.nickname:
  880. print('Specify a nickname with the --nickname option')
  881. sys.exit()
  882. if not args.password:
  883. print('Specify a password with the --password option')
  884. sys.exit()
  885. session = createSession(proxyType)
  886. personCache = {}
  887. cachedWebfingers = {}
  888. print('Sending like of ' + args.like)
  889. sendLikeViaServer(baseDir, session,
  890. args.nickname, args.password,
  891. domain, port,
  892. httpPrefix, args.like,
  893. cachedWebfingers, personCache,
  894. True, __version__)
  895. for i in range(10):
  896. # TODO detect send success/fail
  897. time.sleep(1)
  898. sys.exit()
  899. if args.undolike:
  900. if not args.nickname:
  901. print('Specify a nickname with the --nickname option')
  902. sys.exit()
  903. if not args.password:
  904. print('Specify a password with the --password option')
  905. sys.exit()
  906. session = createSession(proxyType)
  907. personCache = {}
  908. cachedWebfingers = {}
  909. print('Sending undo like of ' + args.undolike)
  910. sendUndoLikeViaServer(baseDir, session,
  911. args.nickname, args.password,
  912. domain, port,
  913. httpPrefix, args.undolike,
  914. cachedWebfingers, personCache,
  915. True, __version__)
  916. for i in range(10):
  917. # TODO detect send success/fail
  918. time.sleep(1)
  919. sys.exit()
  920. if args.delete:
  921. if not args.nickname:
  922. print('Specify a nickname with the --nickname option')
  923. sys.exit()
  924. if not args.password:
  925. print('Specify a password with the --password option')
  926. sys.exit()
  927. session = createSession(proxyType)
  928. personCache = {}
  929. cachedWebfingers = {}
  930. print('Sending delete request of ' + args.delete)
  931. sendDeleteViaServer(baseDir, session,
  932. args.nickname, args.password,
  933. domain, port,
  934. httpPrefix, args.delete,
  935. cachedWebfingers, personCache,
  936. True, __version__)
  937. for i in range(10):
  938. # TODO detect send success/fail
  939. time.sleep(1)
  940. sys.exit()
  941. if args.follow:
  942. # follow via c2s protocol
  943. if '.' not in args.follow:
  944. print("This doesn't look like a fediverse handle")
  945. sys.exit()
  946. if not args.nickname:
  947. print('Please specify the nickname for the account with --nickname')
  948. sys.exit()
  949. if not args.password:
  950. print('Please specify the password for ' + args.nickname +
  951. ' on ' + domain)
  952. sys.exit()
  953. followNickname = getNicknameFromActor(args.follow)
  954. if not followNickname:
  955. print('Unable to find nickname in ' + args.follow)
  956. sys.exit()
  957. followDomain, followPort = getDomainFromActor(args.follow)
  958. session = createSession(proxyType)
  959. personCache = {}
  960. cachedWebfingers = {}
  961. followHttpPrefix = httpPrefix
  962. if args.follow.startswith('https'):
  963. followHttpPrefix = 'https'
  964. sendFollowRequestViaServer(baseDir, session,
  965. args.nickname, args.password,
  966. domain, port,
  967. followNickname, followDomain, followPort,
  968. httpPrefix,
  969. cachedWebfingers, personCache,
  970. debug, __version__)
  971. for t in range(20):
  972. time.sleep(1)
  973. # TODO some method to know if it worked
  974. print('Ok')
  975. sys.exit()
  976. if args.unfollow:
  977. # unfollow via c2s protocol
  978. if '.' not in args.follow:
  979. print("This doesn't look like a fediverse handle")
  980. sys.exit()
  981. if not args.nickname:
  982. print('Please specify the nickname for the account with --nickname')
  983. sys.exit()
  984. if not args.password:
  985. print('Please specify the password for '+args.nickname+' on '+domain)
  986. sys.exit()
  987. followNickname = getNicknameFromActor(args.unfollow)
  988. if not followNickname:
  989. print('WARN: unable to find nickname in ' + args.unfollow)
  990. sys.exit()
  991. followDomain, followPort = getDomainFromActor(args.unfollow)
  992. session = createSession(proxyType)
  993. personCache = {}
  994. cachedWebfingers = {}
  995. followHttpPrefix = httpPrefix
  996. if args.follow.startswith('https'):
  997. followHttpPrefix = 'https'
  998. sendUnfollowRequestViaServer(baseDir, session,
  999. args.nickname, args.password,
  1000. domain, port,
  1001. followNickname, followDomain, followPort,
  1002. httpPrefix,
  1003. cachedWebfingers, personCache,
  1004. debug, __version__)
  1005. for t in range(20):
  1006. time.sleep(1)
  1007. # TODO some method to know if it worked
  1008. print('Ok')
  1009. sys.exit()
  1010. nickname = 'admin'
  1011. if args.domain:
  1012. domain = args.domain
  1013. setConfigParam(baseDir, 'domain', domain)
  1014. if args.port:
  1015. port = args.port
  1016. setConfigParam(baseDir, 'port', port)
  1017. if args.proxyPort:
  1018. proxyPort = args.proxyPort
  1019. setConfigParam(baseDir, 'proxyPort', proxyPort)
  1020. ocapAlways = False
  1021. if args.ocap:
  1022. ocapAlways = args.ocap
  1023. if args.gnunet:
  1024. httpPrefix = 'gnunet'
  1025. if args.dat:
  1026. httpPrefix = 'dat'
  1027. if args.hyper:
  1028. httpPrefix = 'hyper'
  1029. if args.i2p:
  1030. httpPrefix = 'http'
  1031. if args.actor:
  1032. originalActor = args.actor
  1033. if '/@' in args.actor or \
  1034. '/users/' in args.actor or \
  1035. args.actor.startswith('http') or \
  1036. args.actor.startswith('dat'):
  1037. # format: https://domain/@nick
  1038. prefixes = getProtocolPrefixes()
  1039. for prefix in prefixes:
  1040. args.actor = args.actor.replace(prefix, '')
  1041. args.actor = args.actor.replace('/@', '/users/')
  1042. if '/users/' not in args.actor and \
  1043. '/channel/' not in args.actor and \
  1044. '/profile/' not in args.actor:
  1045. print('Expected actor format: ' +
  1046. 'https://domain/@nick or https://domain/users/nick')
  1047. sys.exit()
  1048. if '/users/' in args.actor:
  1049. nickname = args.actor.split('/users/')[1]
  1050. nickname = nickname.replace('\n', '').replace('\r', '')
  1051. domain = args.actor.split('/users/')[0]
  1052. elif '/profile/' in args.actor:
  1053. nickname = args.actor.split('/profile/')[1]
  1054. nickname = nickname.replace('\n', '').replace('\r', '')
  1055. domain = args.actor.split('/profile/')[0]
  1056. else:
  1057. nickname = args.actor.split('/channel/')[1]
  1058. nickname = nickname.replace('\n', '').replace('\r', '')
  1059. domain = args.actor.split('/channel/')[0]
  1060. else:
  1061. # format: @nick@domain
  1062. if '@' not in args.actor:
  1063. print('Syntax: --actor nickname@domain')
  1064. sys.exit()
  1065. if args.actor.startswith('@'):
  1066. args.actor = args.actor[1:]
  1067. if '@' not in args.actor:
  1068. print('Syntax: --actor nickname@domain')
  1069. sys.exit()
  1070. nickname = args.actor.split('@')[0]
  1071. domain = args.actor.split('@')[1]
  1072. domain = domain.replace('\n', '').replace('\r', '')
  1073. cachedWebfingers = {}
  1074. if args.http or domain.endswith('.onion'):
  1075. httpPrefix = 'http'
  1076. port = 80
  1077. proxyType = 'tor'
  1078. elif domain.endswith('.i2p'):
  1079. httpPrefix = 'http'
  1080. port = 80
  1081. proxyType = 'i2p'
  1082. elif args.gnunet:
  1083. httpPrefix = 'gnunet'
  1084. port = 80
  1085. proxyType = 'gnunet'
  1086. else:
  1087. httpPrefix = 'https'
  1088. port = 443
  1089. session = createSession(proxyType)
  1090. if nickname == 'inbox':
  1091. nickname = domain
  1092. handle = nickname + '@' + domain
  1093. wfRequest = webfingerHandle(session, handle,
  1094. httpPrefix, cachedWebfingers,
  1095. None, __version__)
  1096. if not wfRequest:
  1097. print('Unable to webfinger ' + handle)
  1098. sys.exit()
  1099. if not isinstance(wfRequest, dict):
  1100. print('Webfinger for ' + handle + ' did not return a dict. ' +
  1101. str(wfRequest))
  1102. sys.exit()
  1103. pprint(wfRequest)
  1104. personUrl = None
  1105. if wfRequest.get('errors'):
  1106. print('wfRequest error: ' + str(wfRequest['errors']))
  1107. if '/users/' in args.actor or \
  1108. '/profile/' in args.actor or \
  1109. '/channel/' in args.actor:
  1110. personUrl = originalActor
  1111. else:
  1112. sys.exit()
  1113. profileStr = 'https://www.w3.org/ns/activitystreams'
  1114. asHeader = {
  1115. 'Accept': 'application/activity+json; profile="' + profileStr + '"'
  1116. }
  1117. if not personUrl:
  1118. personUrl = getUserUrl(wfRequest)
  1119. if nickname == domain:
  1120. personUrl = personUrl.replace('/users/', '/actor/')
  1121. personUrl = personUrl.replace('/channel/', '/actor/')
  1122. personUrl = personUrl.replace('/profile/', '/actor/')
  1123. if not personUrl:
  1124. # try single user instance
  1125. personUrl = httpPrefix + '://' + domain
  1126. profileStr = 'https://www.w3.org/ns/activitystreams'
  1127. asHeader = {
  1128. 'Accept': 'application/ld+json; profile="' + profileStr + '"'
  1129. }
  1130. if '/channel/' in personUrl:
  1131. profileStr = 'https://www.w3.org/ns/activitystreams'
  1132. asHeader = {
  1133. 'Accept': 'application/ld+json; profile="' + profileStr + '"'
  1134. }
  1135. personJson = \
  1136. getJson(session, personUrl, asHeader, None, __version__,
  1137. httpPrefix, None)
  1138. if personJson:
  1139. pprint(personJson)
  1140. else:
  1141. profileStr = 'https://www.w3.org/ns/activitystreams'
  1142. asHeader = {
  1143. 'Accept': 'application/jrd+json; profile="' + profileStr + '"'
  1144. }
  1145. personJson = \
  1146. getJson(session, personUrl, asHeader, None,
  1147. __version__, httpPrefix, None)
  1148. if personJson:
  1149. pprint(personJson)
  1150. else:
  1151. print('Failed to get ' + personUrl)
  1152. sys.exit()
  1153. if args.addaccount:
  1154. if '@' in args.addaccount:
  1155. nickname = args.addaccount.split('@')[0]
  1156. domain = args.addaccount.split('@')[1]
  1157. else:
  1158. nickname = args.addaccount
  1159. if not args.domain or not getConfigParam(baseDir, 'domain'):
  1160. print('Use the --domain option to set the domain name')
  1161. sys.exit()
  1162. if not validNickname(domain, nickname):
  1163. print(nickname + ' is a reserved name. Use something different.')
  1164. sys.exit()
  1165. if not args.password:
  1166. print('Use the --password option to set the password for ' + nickname)
  1167. sys.exit()
  1168. if len(args.password.strip()) < 8:
  1169. print('Password should be at least 8 characters')
  1170. sys.exit()
  1171. if os.path.isdir(baseDir + '/accounts/' + nickname + '@' + domain):
  1172. print('Account already exists')
  1173. sys.exit()
  1174. if os.path.isdir(baseDir + '/deactivated/' + nickname + '@' + domain):
  1175. print('Account is deactivated')
  1176. sys.exit()
  1177. createPerson(baseDir, nickname, domain, port, httpPrefix,
  1178. True, not args.noapproval, args.password.strip())
  1179. if os.path.isdir(baseDir + '/accounts/' + nickname + '@' + domain):
  1180. print('Account created for ' + nickname + '@' + domain)
  1181. else:
  1182. print('Account creation failed')
  1183. sys.exit()
  1184. if args.addgroup:
  1185. if '@' in args.addgroup:
  1186. nickname = args.addgroup.split('@')[0]
  1187. domain = args.addgroup.split('@')[1]
  1188. else:
  1189. nickname = args.addgroup
  1190. if not args.domain or not getConfigParam(baseDir, 'domain'):
  1191. print('Use the --domain option to set the domain name')
  1192. sys.exit()
  1193. if not validNickname(domain, nickname):
  1194. print(nickname + ' is a reserved name. Use something different.')
  1195. sys.exit()
  1196. if not args.password:
  1197. print('Use the --password option to set the password for ' + nickname)
  1198. sys.exit()
  1199. if len(args.password.strip()) < 8:
  1200. print('Password should be at least 8 characters')
  1201. sys.exit()
  1202. if os.path.isdir(baseDir + '/accounts/' + nickname + '@' + domain):
  1203. print('Group already exists')
  1204. sys.exit()
  1205. createGroup(baseDir, nickname, domain, port, httpPrefix,
  1206. True, args.password.strip())
  1207. if os.path.isdir(baseDir + '/accounts/' + nickname + '@' + domain):
  1208. print('Group created for ' + nickname + '@' + domain)
  1209. else:
  1210. print('Group creation failed')
  1211. sys.exit()
  1212. if args.rmgroup:
  1213. args.rmaccount = args.rmgroup
  1214. if args.deactivate:
  1215. args.rmaccount = args.deactivate
  1216. if args.rmaccount:
  1217. if '@' in args.rmaccount:
  1218. nickname = args.rmaccount.split('@')[0]
  1219. domain = args.rmaccount.split('@')[1]
  1220. else:
  1221. nickname = args.rmaccount
  1222. if not args.domain or not getConfigParam(baseDir, 'domain'):
  1223. print('Use the --domain option to set the domain name')
  1224. sys.exit()
  1225. if args.deactivate:
  1226. if deactivateAccount(baseDir, nickname, domain):
  1227. print('Account for ' + nickname + '@' + domain +
  1228. ' was deactivated')
  1229. else:
  1230. print('Account for ' + nickname + '@' + domain + ' was not found')
  1231. sys.exit()
  1232. if removeAccount(baseDir, nickname, domain, port):
  1233. if not args.rmgroup:
  1234. print('Account for ' + nickname + '@' + domain + ' was removed')
  1235. else:
  1236. print('Group ' + nickname + '@' + domain + ' was removed')
  1237. sys.exit()
  1238. if args.activate:
  1239. if '@' in args.activate:
  1240. nickname = args.activate.split('@')[0]
  1241. domain = args.activate.split('@')[1]
  1242. else:
  1243. nickname = args.activate
  1244. if not args.domain or not getConfigParam(baseDir, 'domain'):
  1245. print('Use the --domain option to set the domain name')
  1246. sys.exit()
  1247. if activateAccount(baseDir, nickname, domain):
  1248. print('Account for ' + nickname + '@' + domain + ' was activated')
  1249. else:
  1250. print('Deactivated account for ' + nickname + '@' + domain +
  1251. ' was not found')
  1252. sys.exit()
  1253. if args.changepassword:
  1254. if len(args.changepassword) != 2:
  1255. print('--changepassword [nickname] [new password]')
  1256. sys.exit()
  1257. if '@' in args.changepassword[0]:
  1258. nickname = args.changepassword[0].split('@')[0]
  1259. domain = args.changepassword[0].split('@')[1]
  1260. else:
  1261. nickname = args.changepassword[0]
  1262. if not args.domain or not getConfigParam(baseDir, 'domain'):
  1263. print('Use the --domain option to set the domain name')
  1264. sys.exit()
  1265. newPassword = args.changepassword[1]
  1266. if len(newPassword) < 8:
  1267. print('Password should be at least 8 characters')
  1268. sys.exit()
  1269. if not os.path.isdir(baseDir + '/accounts/' + nickname + '@' + domain):
  1270. print('Account ' + nickname + '@' + domain + ' not found')
  1271. sys.exit()
  1272. passwordFile = baseDir + '/accounts/passwords'
  1273. if os.path.isfile(passwordFile):
  1274. if nickname + ':' in open(passwordFile).read():
  1275. storeBasicCredentials(baseDir, nickname, newPassword)
  1276. print('Password for ' + nickname + ' was changed')
  1277. else:
  1278. print(nickname + ' is not in the passwords file')
  1279. else:
  1280. print('Passwords file not found')
  1281. sys.exit()
  1282. archiveWeeks = 4
  1283. if args.archiveWeeks:
  1284. archiveWeeks = args.archiveWeeks
  1285. archiveMaxPosts = 32000
  1286. if args.archiveMaxPosts:
  1287. archiveMaxPosts = args.archiveMaxPosts
  1288. if args.archive:
  1289. if args.archive.lower().endswith('null') or \
  1290. args.archive.lower().endswith('delete') or \
  1291. args.archive.lower().endswith('none'):
  1292. args.archive = None
  1293. print('Archiving with deletion of old posts...')
  1294. else:
  1295. print('Archiving to ' + args.archive + '...')
  1296. archiveMedia(baseDir, args.archive, archiveWeeks)
  1297. archivePosts(baseDir, httpPrefix, args.archive, {}, archiveMaxPosts)
  1298. print('Archiving complete')
  1299. sys.exit()
  1300. if not args.domain and not domain:
  1301. print('Specify a domain with --domain [name]')
  1302. sys.exit()
  1303. if args.avatar:
  1304. if not os.path.isfile(args.avatar):
  1305. print(args.avatar + ' is not an image filename')
  1306. sys.exit()
  1307. if not args.nickname:
  1308. print('Specify a nickname with --nickname [name]')
  1309. sys.exit()
  1310. if setProfileImage(baseDir, httpPrefix, args.nickname, domain,
  1311. port, args.avatar, 'avatar', '128x128'):
  1312. print('Avatar added for ' + args.nickname)
  1313. else:
  1314. print('Avatar was not added for ' + args.nickname)
  1315. sys.exit()
  1316. if args.backgroundImage:
  1317. if not os.path.isfile(args.backgroundImage):
  1318. print(args.backgroundImage + ' is not an image filename')
  1319. sys.exit()
  1320. if not args.nickname:
  1321. print('Specify a nickname with --nickname [name]')
  1322. sys.exit()
  1323. if setProfileImage(baseDir, httpPrefix, args.nickname, domain,
  1324. port, args.backgroundImage, 'background', '256x256'):
  1325. print('Background image added for ' + args.nickname)
  1326. else:
  1327. print('Background image was not added for ' + args.nickname)
  1328. sys.exit()
  1329. if args.project:
  1330. if not args.delegate and not args.undelegate:
  1331. if not nickname:
  1332. print('No nickname given')
  1333. sys.exit()
  1334. if args.role.lower() == 'none' or \
  1335. args.role.lower() == 'remove' or \
  1336. args.role.lower() == 'delete':
  1337. args.role = None
  1338. if args.role:
  1339. if setRole(baseDir, nickname, domain, args.project, args.role):
  1340. print('Role within ' + args.project + ' set to ' + args.role)
  1341. else:
  1342. if setRole(baseDir, nickname, domain, args.project, None):
  1343. print('Left ' + args.project)
  1344. sys.exit()
  1345. if args.skill:
  1346. if not nickname:
  1347. print('Specify a nickname with the --nickname option')
  1348. sys.exit()
  1349. if not args.password:
  1350. print('Specify a password with the --password option')
  1351. sys.exit()
  1352. if not args.skillLevelPercent:
  1353. print('Specify a skill level in the range 0-100')
  1354. sys.exit()
  1355. if int(args.skillLevelPercent) < 0 or \
  1356. int(args.skillLevelPercent) > 100:
  1357. print('Skill level should be a percentage in the range 0-100')
  1358. sys.exit()
  1359. session = createSession(proxyType)
  1360. personCache = {}
  1361. cachedWebfingers = {}
  1362. print('Sending ' + args.skill + ' skill level ' +
  1363. str(args.skillLevelPercent) + ' for ' + nickname)
  1364. sendSkillViaServer(baseDir, session,
  1365. nickname, args.password,
  1366. domain, port,
  1367. httpPrefix,
  1368. args.skill, args.skillLevelPercent,
  1369. cachedWebfingers, personCache,
  1370. True, __version__)
  1371. for i in range(10):
  1372. # TODO detect send success/fail
  1373. time.sleep(1)
  1374. sys.exit()
  1375. if args.availability:
  1376. if not nickname:
  1377. print('Specify a nickname with the --nickname option')
  1378. sys.exit()
  1379. if not args.password:
  1380. print('Specify a password with the --password option')
  1381. sys.exit()
  1382. session = createSession(proxyType)
  1383. personCache = {}
  1384. cachedWebfingers = {}
  1385. print('Sending availability status of ' + nickname +
  1386. ' as ' + args.availability)
  1387. sendAvailabilityViaServer(baseDir, session, nickname, args.password,
  1388. domain, port,
  1389. httpPrefix,
  1390. args.availability,
  1391. cachedWebfingers, personCache,
  1392. True, __version__)
  1393. for i in range(10):
  1394. # TODO detect send success/fail
  1395. time.sleep(1)
  1396. sys.exit()
  1397. if federationList:
  1398. print('Federating with: ' + str(federationList))
  1399. if args.block:
  1400. if not nickname:
  1401. print('Specify a nickname with the --nickname option')
  1402. sys.exit()
  1403. if not args.password:
  1404. print('Specify a password with the --password option')
  1405. sys.exit()
  1406. if '@' in args.block:
  1407. blockedDomain = args.block.split('@')[1]
  1408. blockedDomain = blockedDomain.replace('\n', '').replace('\r', '')
  1409. blockedNickname = args.block.split('@')[0]
  1410. blockedActor = httpPrefix + '://' + blockedDomain + \
  1411. '/users/' + blockedNickname
  1412. args.block = blockedActor
  1413. else:
  1414. if '/users/' not in args.block:
  1415. print(args.block + ' does not look like an actor url')
  1416. sys.exit()
  1417. session = createSession(proxyType)
  1418. personCache = {}
  1419. cachedWebfingers = {}
  1420. print('Sending block of ' + args.block)
  1421. sendBlockViaServer(baseDir, session, nickname, args.password,
  1422. domain, port,
  1423. httpPrefix, args.block,
  1424. cachedWebfingers, personCache,
  1425. True, __version__)
  1426. for i in range(10):
  1427. # TODO detect send success/fail
  1428. time.sleep(1)
  1429. sys.exit()
  1430. if args.delegate:
  1431. if not nickname:
  1432. print('Specify a nickname with the --nickname option')
  1433. sys.exit()
  1434. if not args.password:
  1435. print('Specify a password with the --password option')
  1436. sys.exit()
  1437. if not args.project:
  1438. print('Specify a project with the --project option')
  1439. sys.exit()
  1440. if not args.role:
  1441. print('Specify a role with the --role option')
  1442. sys.exit()
  1443. if '@' in args.delegate:
  1444. delegatedNickname = args.delegate.split('@')[0]
  1445. args.delegate = blockedActor
  1446. session = createSession(proxyType)
  1447. personCache = {}
  1448. cachedWebfingers = {}
  1449. print('Sending delegation for ' + args.delegate +
  1450. ' with role ' + args.role + ' in project ' + args.project)
  1451. sendRoleViaServer(baseDir, session,
  1452. nickname, args.password,
  1453. domain, port,
  1454. httpPrefix, args.delegate,
  1455. args.project, args.role,
  1456. cachedWebfingers, personCache,
  1457. True, __version__)
  1458. for i in range(10):
  1459. # TODO detect send success/fail
  1460. time.sleep(1)
  1461. sys.exit()
  1462. if args.undelegate:
  1463. if not nickname:
  1464. print('Specify a nickname with the --nickname option')
  1465. sys.exit()
  1466. if not args.password:
  1467. print('Specify a password with the --password option')
  1468. sys.exit()
  1469. if not args.project:
  1470. print('Specify a project with the --project option')
  1471. sys.exit()
  1472. if '@' in args.undelegate:
  1473. delegatedNickname = args.undelegate.split('@')[0]
  1474. args.undelegate = blockedActor
  1475. session = createSession(proxyType)
  1476. personCache = {}
  1477. cachedWebfingers = {}
  1478. print('Sending delegation removal for ' + args.undelegate +
  1479. ' from role ' + args.role + ' in project ' + args.project)
  1480. sendRoleViaServer(baseDir, session,
  1481. nickname, args.password,
  1482. domain, port,
  1483. httpPrefix, args.delegate,
  1484. args.project, None,
  1485. cachedWebfingers, personCache,
  1486. True, __version__)
  1487. for i in range(10):
  1488. # TODO detect send success/fail
  1489. time.sleep(1)
  1490. sys.exit()
  1491. if args.unblock:
  1492. if not nickname:
  1493. print('Specify a nickname with the --nickname option')
  1494. sys.exit()
  1495. if not args.password:
  1496. print('Specify a password with the --password option')
  1497. sys.exit()
  1498. if '@' in args.unblock:
  1499. blockedDomain = args.unblock.split('@')[1]
  1500. blockedDomain = blockedDomain.replace('\n', '').replace('\r', '')
  1501. blockedNickname = args.unblock.split('@')[0]
  1502. blockedActor = httpPrefix + '://' + blockedDomain + \
  1503. '/users/' + blockedNickname
  1504. args.unblock = blockedActor
  1505. else:
  1506. if '/users/' not in args.unblock:
  1507. print(args.unblock + ' does not look like an actor url')
  1508. sys.exit()
  1509. session = createSession(proxyType)
  1510. personCache = {}
  1511. cachedWebfingers = {}
  1512. print('Sending undo block of ' + args.unblock)
  1513. sendUndoBlockViaServer(baseDir, session, nickname, args.password,
  1514. domain, port,
  1515. httpPrefix, args.unblock,
  1516. cachedWebfingers, personCache,
  1517. True, __version__)
  1518. for i in range(10):
  1519. # TODO detect send success/fail
  1520. time.sleep(1)
  1521. sys.exit()
  1522. if args.filterStr:
  1523. if not args.nickname:
  1524. print('Please specify a nickname')
  1525. sys.exit()
  1526. if addFilter(baseDir, args.nickname, domain, args.filterStr):
  1527. print('Filter added to ' + args.nickname + ': ' + args.filterStr)
  1528. sys.exit()
  1529. if args.unfilterStr:
  1530. if not args.nickname:
  1531. print('Please specify a nickname')
  1532. sys.exit()
  1533. if removeFilter(baseDir, args.nickname, domain, args.unfilterStr):
  1534. print('Filter removed from ' + args.nickname + ': ' + args.unfilterStr)
  1535. sys.exit()
  1536. if args.testdata:
  1537. useBlurhash = False
  1538. nickname = 'testuser567'
  1539. password = 'boringpassword'
  1540. print('Generating some test data for user: ' + nickname)
  1541. if os.path.isdir(baseDir + '/tags'):
  1542. shutil.rmtree(baseDir + '/tags')
  1543. if os.path.isdir(baseDir + '/accounts'):
  1544. shutil.rmtree(baseDir + '/accounts')
  1545. if os.path.isdir(baseDir + '/keys'):
  1546. shutil.rmtree(baseDir + '/keys')
  1547. if os.path.isdir(baseDir + '/media'):
  1548. shutil.rmtree(baseDir + '/media')
  1549. if os.path.isdir(baseDir + '/sharefiles'):
  1550. shutil.rmtree(baseDir + '/sharefiles')
  1551. if os.path.isdir(baseDir + '/wfendpoints'):
  1552. shutil.rmtree(baseDir + '/wfendpoints')
  1553. setConfigParam(baseDir, 'registrationsRemaining',
  1554. str(maxRegistrations))
  1555. createPerson(baseDir, 'maxboardroom', domain, port, httpPrefix,
  1556. True, False, password)
  1557. createPerson(baseDir, 'ultrapancake', domain, port, httpPrefix,
  1558. True, False, password)
  1559. createPerson(baseDir, 'drokk', domain, port, httpPrefix,
  1560. True, False, password)
  1561. createPerson(baseDir, 'sausagedog', domain, port, httpPrefix,
  1562. True, False, password)
  1563. createPerson(baseDir, nickname, domain, port, httpPrefix,
  1564. True, False, 'likewhateveryouwantscoob')
  1565. setSkillLevel(baseDir, nickname, domain, 'testing', 60)
  1566. setSkillLevel(baseDir, nickname, domain, 'typing', 50)
  1567. setRole(baseDir, nickname, domain, 'instance', 'admin')
  1568. setRole(baseDir, nickname, domain, 'epicyon', 'hacker')
  1569. setRole(baseDir, nickname, domain, 'someproject', 'assistant')
  1570. setAvailability(baseDir, nickname, domain, 'busy')
  1571. addShare(baseDir,
  1572. httpPrefix, nickname, domain, port,
  1573. "spanner",
  1574. "It's a spanner",
  1575. "img/shares1.png",
  1576. "tool",
  1577. "mechanical",
  1578. "City",
  1579. "2 months",
  1580. debug)
  1581. addShare(baseDir,
  1582. httpPrefix, nickname, domain, port,
  1583. "witch hat",
  1584. "Spooky",
  1585. "img/shares2.png",
  1586. "hat",
  1587. "clothing",
  1588. "City",
  1589. "3 months",
  1590. debug)
  1591. deleteAllPosts(baseDir, nickname, domain, 'inbox')
  1592. deleteAllPosts(baseDir, nickname, domain, 'outbox')
  1593. createPublicPost(baseDir, nickname, domain, port, httpPrefix,
  1594. "like, this is totally just a #test, man",
  1595. False, True, False, None, None, useBlurhash)
  1596. createPublicPost(baseDir, nickname, domain, port, httpPrefix,
  1597. "Zoiks!!!",
  1598. False, True, False, None, None, useBlurhash)
  1599. createPublicPost(baseDir, nickname, domain, port, httpPrefix,
  1600. "Hey scoob we need like a hundred more #milkshakes",
  1601. False, True, False, None, None, useBlurhash)
  1602. createPublicPost(baseDir, nickname, domain, port, httpPrefix,
  1603. "Getting kinda spooky around here",
  1604. False, True, False, None, None, useBlurhash, 'someone')
  1605. createPublicPost(baseDir, nickname, domain, port, httpPrefix,
  1606. "And they would have gotten away with it too" +
  1607. "if it wasn't for those pesky hackers",
  1608. False, True, False, 'img/logo.png',
  1609. 'Description of image', useBlurhash)
  1610. createPublicPost(baseDir, nickname, domain, port, httpPrefix,
  1611. "man, these centralized sites are, like, the worst!",
  1612. False, True, False, None, None, useBlurhash)
  1613. createPublicPost(baseDir, nickname, domain, port, httpPrefix,
  1614. "another mystery solved #test",
  1615. False, True, False, None, None, useBlurhash)
  1616. createPublicPost(baseDir, nickname, domain, port, httpPrefix,
  1617. "let's go bowling",
  1618. False, True, False, None, None, useBlurhash)
  1619. domainFull = domain + ':' + str(port)
  1620. clearFollows(baseDir, nickname, domain)
  1621. followPerson(baseDir, nickname, domain, 'maxboardroom', domainFull,
  1622. federationList, False)
  1623. followPerson(baseDir, nickname, domain, 'ultrapancake', domainFull,
  1624. federationList, False)
  1625. followPerson(baseDir, nickname, domain, 'sausagedog', domainFull,
  1626. federationList, False)
  1627. followPerson(baseDir, nickname, domain, 'drokk', domainFull,
  1628. federationList, False)
  1629. followerOfPerson(baseDir, nickname, domain, 'drokk', domainFull,
  1630. federationList, False)
  1631. followerOfPerson(baseDir, nickname, domain, 'maxboardroom', domainFull,
  1632. federationList, False)
  1633. setConfigParam(baseDir, 'admin', nickname)
  1634. # set a lower bound to the maximum mentions
  1635. # so that it can't be accidentally set to zero and disable replies
  1636. if args.maxMentions < 4:
  1637. args.maxMentions = 4
  1638. registration = getConfigParam(baseDir, 'registration')
  1639. if not registration:
  1640. registration = False
  1641. YTDomain = getConfigParam(baseDir, 'youtubedomain')
  1642. if YTDomain:
  1643. if '://' in YTDomain:
  1644. YTDomain = YTDomain.split('://')[1]
  1645. if '/' in YTDomain:
  1646. YTDomain = YTDomain.split('/')[0]
  1647. if '.' in YTDomain:
  1648. args.YTReplacementDomain = YTDomain
  1649. if setTheme(baseDir, themeName):
  1650. print('Theme set to ' + themeName)
  1651. runDaemon(args.blogsinstance, args.mediainstance,
  1652. args.maxRecentPosts,
  1653. not args.nosharedinbox,
  1654. registration, args.language, __version__,
  1655. instanceId, args.client, baseDir,
  1656. domain, onionDomain, i2pDomain,
  1657. args.YTReplacementDomain,
  1658. port, proxyPort, httpPrefix,
  1659. federationList, args.maxMentions,
  1660. args.maxEmoji, args.authenticatedFetch,
  1661. args.noreply, args.nolike, args.nopics,
  1662. args.noannounce, args.cw, ocapAlways,
  1663. proxyType, args.maxReplies,
  1664. args.domainMaxPostsPerDay,
  1665. args.accountMaxPostsPerDay,
  1666. args.allowdeletion, debug, False,
  1667. args.instanceOnlySkillsSearch, [],
  1668. args.blurhash, not args.noapproval)