write-tweet.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. import { Component } from "@angular/core";
  2. import {
  3. IonicPage,
  4. NavController,
  5. NavParams,
  6. LoadingController,
  7. AlertController
  8. } from "ionic-angular";
  9. import {
  10. FormBuilder,
  11. Validators,
  12. FormGroup,
  13. ValidatorFn,
  14. AbstractControl
  15. } from "@angular/forms";
  16. import { TwitterApiProvider } from "../../providers/twitter-api/twitter-api";
  17. import { Storage } from "@ionic/storage";
  18. import { P2pStorageIpfsProvider } from "../../providers/p2p-storage-ipfs/p2p-storage-ipfs";
  19. import { P2pDatabaseGunProvider } from "../../providers/p2p-database-gun/p2p-database-gun";
  20. import twittertext from "twitter-text";
  21. import { CryptoProvider } from "../../providers/crypto/crypto";
  22. import * as openpgp from 'openpgp';
  23. @IonicPage()
  24. @Component({
  25. selector: "page-write-tweet",
  26. templateUrl: "write-tweet.html"
  27. })
  28. export class WriteTweetPage {
  29. tweet: FormGroup;
  30. retweetId: string;
  31. replyToStatusId: string;
  32. retweet;
  33. replyTweet;
  34. openpgp;
  35. privateKey;
  36. publicKey;
  37. pk: any[]=[];
  38. passp = 'super long and hard to guess secret' ;
  39. constructor(
  40. public navCtrl: NavController,
  41. public navParams: NavParams,
  42. private formBuilder: FormBuilder,
  43. private twitter: TwitterApiProvider,
  44. private loadingCtrl: LoadingController,
  45. private storage: Storage,
  46. private ipfs: P2pStorageIpfsProvider,
  47. private gun: P2pDatabaseGunProvider,
  48. private cryptoUtils: CryptoProvider,
  49. private alertCtrl: AlertController
  50. ) {
  51. this.retweetId = this.navParams.get("tweetId");
  52. this.replyToStatusId = this.navParams.get("replyToStatus");
  53. this.tweet = this.formBuilder.group({
  54. text: [""],
  55. p2p: [false]
  56. });
  57. this.addValidators();
  58. this.encryptDecryptFunction();
  59. }
  60. public async encryptDecryptFunction () {
  61. await openpgp.initWorker({path:'assets/scripts/openpgp.worker.js'});
  62. let a = await this.generateKeys();
  63. console.log('a is:',a.publicKeyArmored);
  64. let b = await this.generateKeys();
  65. console.log('b is:',b.publicKeyArmored);
  66. let c = await this.generateKeys();
  67. this.privateKey =c.privateKeyArmored;
  68. this.publicKey = c.publicKeyArmored;
  69. this.pk.push(a.publicKeyArmored);
  70. this.pk.push(b.publicKeyArmored);
  71. // this.pk = [`----BEGIN PGP PUBLIC KEY BLOCK-----
  72. // Version: OpenPGP.js v4.7.1
  73. // Comment: https://openpgpjs.org
  74. // xjMEXfAn1xYJKwYBBAHaRw8BAQdAAMVNOABw8MBtrtYR8KC3tSro3wITyApT
  75. // TVjKVCppD+DNG0pvbiBTbWl0aCA8am9uQGV4YW1wbGUuY29tPsJ3BBAWCgAf
  76. // BQJd8CfXBgsJBwgDAgQVCAoCAxYCAQIZAQIbAwIeAQAKCRD+efBRXzuMsfA7
  77. // AQCEgoToFzv2hT9BREdiQp531/AHSyoZWmWvSZSvmga40gD8C+zwbCySnkhQ
  78. // pb4L0DCKtSDa7pLg2g0OcxJlbSZWHQ3OOARd8CfXEgorBgEEAZdVAQUBAQdA
  79. // p4mVY17dPWf6VCBqW10Ybk5JgUO6FK0OsETWw3gG2zcDAQgHwmEEGBYIAAkF
  80. // Al3wJ9cCGwwACgkQ/nnwUV87jLFHbAD9GyoL7dcTDGQoqtrhKozdgnzfugTb
  81. // er0bwU15WNMjefkA/jEqK9YUNcRrFKIuac9PVibGgutL8ak7ukysw6iTcCsM
  82. // =fmhE
  83. // -----END PGP PUBLIC KEY BLOCK-----`,
  84. // `-----BEGIN PGP PUBLIC KEY BLOCK-----
  85. // Version: OpenPGP.js v4.7.1
  86. // Comment: https://openpgpjs.org
  87. // xjMEXfAn1hYJKwYBBAHaRw8BAQdAsF1ivpd0HU8ogj02LDv6BTOxNMWGZaEc
  88. // OyZBwqoYJPrNG0pvbiBTbWl0aCA8am9uQGV4YW1wbGUuY29tPsJ3BBAWCgAf
  89. // BQJd8CfWBgsJBwgDAgQVCAoCAxYCAQIZAQIbAwIeAQAKCRDEruv77flRJ32B
  90. // AP93GIBcUW2okROoZZhdPVeqjRD72Ft64imXpdZ0jx4ohgEA5Kv9vs2kV73q
  91. // k6fcdf7qD/i5gMExU0+vV05c9VxBYwfOOARd8CfWEgorBgEEAZdVAQUBAQdA
  92. // 1J7E03ZopUnsIeNzeiZvba6qxhhUbpmBZ1aN1HhWUlEDAQgHwmEEGBYIAAkF
  93. // Al3wJ9YCGwwACgkQxK7r++35USdTqQD/ZEg8X5tMx75nQe4mGlyiRjmmtWLw
  94. // n9bslTdjBIszs/EA/R1WIm6ji4Ru1dJWc3ISisz78xTM2H8U7fnP8yjFcWcD
  95. // =hgnW
  96. // -----END PGP PUBLIC KEY BLOCK-----`];
  97. console.log('array of pub keys is :',this.pk);
  98. this.pk = this.pk.map(async (key) => {
  99. return (await openpgp.key.readArmored(key)).keys[0]
  100. });
  101. console.log('priv key: ',this.privateKey,'this.pubkey',this.pk);
  102. let encrypted;
  103. const privKeyObj = (await openpgp.key.readArmored(this.privateKey)).keys[0];
  104. console.log('privKeyObj',privKeyObj);
  105. const bla = await privKeyObj.decrypt(this.passp);
  106. // const options = {
  107. // message: openpgp.message.fromText('Hello, World!'), // input as Message object
  108. // publicKeys: (await openpgp.key.readArmored(this.publicKey)).keys, // for encryption
  109. // privateKeys: [privKeyObj] // for signing (optional)
  110. // }
  111. const options = {
  112. message: openpgp.message.fromText('Hello, World!'), // input as Message object
  113. publicKeys: await Promise.all(this.pk), // for encryption
  114. privateKeys: [privKeyObj] // for signing (optional)
  115. }
  116. const ciphertext = await openpgp.encrypt(options);
  117. encrypted = ciphertext.data; // '-----BEGIN PGP MESSAGE ... END PGP MESSAGE-----'
  118. console.log('encrypted is:',encrypted);
  119. let aprivKeyObj = (await openpgp.key.readArmored(a.privateKeyArmored)).keys[0];
  120. await aprivKeyObj.decrypt(this.passp);
  121. const options2 = {
  122. message: await openpgp.message.readArmored(encrypted), // parse armored message
  123. privateKeys: [aprivKeyObj] // for decryption
  124. }
  125. console.log('options2 is: ',options2);
  126. let plaintext = await openpgp.decrypt(options2);
  127. console.log('decrypted text is:',plaintext,plaintext.data);
  128. return plaintext.data // 'Hello, World!'
  129. }
  130. public async generateKeys(){
  131. let options = {
  132. userIds: [{ name:'Jon Smith', email:'jon@example.com' }], // multiple user IDs
  133. curve: "ed25519", // ECC curve name
  134. passphrase: this.passp // protects the private key
  135. };
  136. let a = await openpgp.generateKey(options);
  137. return a;
  138. // console.log('resolved a = ',a);
  139. // this.privateKey =a.privateKeyArmored;
  140. // this.publicKey = a.publicKeyArmored;
  141. // this.encryptDecryptFunction();
  142. }
  143. private async addValidators() {
  144. const triggerWords = await this.storage.get("keywords");
  145. const validators = [
  146. Validators.maxLength(140),
  147. this.containsTriggerWord(triggerWords)
  148. ];
  149. this.tweet.controls["text"].setValidators(validators);
  150. }
  151. private containsTriggerWord(triggerWords: string): ValidatorFn {
  152. return (control: AbstractControl): { [key: string]: any } | null => {
  153. if (triggerWords) {
  154. const regexList = triggerWords
  155. .toLowerCase()
  156. .split(", ")
  157. .join("|");
  158. const regex = new RegExp(regexList);
  159. const containsTriggerWord = regex.test(control.value.toLowerCase());
  160. return containsTriggerWord
  161. ? { containsTriggerWord: { value: control.value } }
  162. : null;
  163. } else {
  164. return null;
  165. }
  166. };
  167. }
  168. async ionViewDidLoad() {
  169. if (this.retweetId) {
  170. this.retweet = await this.twitter.fetchTweet(this.retweetId);
  171. }
  172. if (this.replyToStatusId) {
  173. this.replyTweet = await this.twitter.fetchTweet(this.replyToStatusId);
  174. }
  175. }
  176. get tweetCharProgress() {
  177. const progress = 1 - this.tweet.value["text"].length / 140;
  178. const radius = 8;
  179. const circumference = Math.PI * radius * 2;
  180. return progress * circumference;
  181. }
  182. get showTrigger(): boolean {
  183. return (
  184. this.tweet &&
  185. this.tweet.controls &&
  186. this.tweet.controls.text &&
  187. this.tweet.controls.text.errors &&
  188. this.tweet.controls.text.errors["containsTriggerWord"] &&
  189. !this.tweet.controls.p2p.value
  190. );
  191. }
  192. showTriggerInfo() {
  193. this.alertCtrl
  194. .create({
  195. title: "Watch Out!",
  196. message:
  197. "Your tweet contains words you have previously defined to only share securely via P2P. Currently P2P mode is not selected.",
  198. buttons: ["OK"]
  199. })
  200. .present();
  201. }
  202. async submitTweet() {
  203. const loading = this.loadingCtrl.create();
  204. loading.present();
  205. if (this.tweet.value.p2p) {
  206. loading.setContent("Validate keys...");
  207. if (
  208. (await this.cryptoUtils.isPrivateKeySet()) &&
  209. (await this.cryptoUtils.isPublicKeyPublished())
  210. ) {
  211. loading.setContent("Publish private tweet...");
  212. await this.tweetPrivate();
  213. } else {
  214. loading.dismiss();
  215. const alert = this.alertCtrl.create({
  216. title: "Oooops...",
  217. message:
  218. "Please verify that you have set a private and public key in the settings and that your latest public key was published."
  219. });
  220. alert.present();
  221. return;
  222. }
  223. } else {
  224. loading.setContent("Publish on Twitter...");
  225. await this.twitter.tweet(
  226. this.tweet.value["text"],
  227. this.retweet,
  228. this.replyToStatusId
  229. );
  230. }
  231. loading.dismiss();
  232. this.navCtrl.pop();
  233. }
  234. private async tweetPrivate() {
  235. const tweet = await this.buildPrivateTweet();
  236. const privateKey = await this.storage.get("privateKey");
  237. const encryptedTweet = this.cryptoUtils.encrypt(
  238. JSON.stringify(tweet),
  239. privateKey
  240. );
  241. const res = await this.ipfs.storeTweet(encryptedTweet);
  242. this.gun.storeLastTweetHashForUser(
  243. tweet.user_id,
  244. res["Hash"],
  245. tweet.created_at
  246. );
  247. this.gun.publishHashtags(tweet.entities.hashtags);
  248. }
  249. private async buildPrivateTweet() {
  250. const status = this.tweet.value["text"].trim();
  251. const entities = await this.getEntities(status);
  252. return {
  253. full_text: status,
  254. user_id: await this.storage.get("userId"),
  255. created_at: Date.now(),
  256. private_tweet: true,
  257. in_reply_to_status_id: this.replyToStatusId,
  258. quoted_status_id: this.retweetId,
  259. display_text_range: [0, status.length],
  260. entities: entities
  261. };
  262. }
  263. private async getEntities(status: string) {
  264. return {
  265. hashtags: twittertext.extractHashtagsWithIndices(status),
  266. urls: twittertext.extractUrlsWithIndices(status),
  267. user_mentions: await this.getMentions(status)
  268. };
  269. }
  270. private async getMentions(status: string) {
  271. // extract mentions
  272. const entities = twittertext.extractMentionsWithIndices(status);
  273. // add user_id
  274. const entitiesWithPromises = entities.map(async mention => {
  275. try {
  276. const user = await this.twitter.fetchUserFromScreenName(
  277. mention.screenName
  278. );
  279. mention["id_str"] = user[0]["id_str"];
  280. mention["screen_name"] = mention.screenName;
  281. delete mention.screenName;
  282. } catch (err) {
  283. console.error(
  284. "There is no user signed up to twitter with username: " +
  285. mention.screenName
  286. );
  287. }
  288. return mention;
  289. });
  290. // filter for valid users and return
  291. return (await Promise.all(entitiesWithPromises)).filter(el =>
  292. el.hasOwnProperty("id_str")
  293. );
  294. }
  295. }