├── pork.icns
├── pork.png
├── package.json
├── main.js
├── code
├── homepage.js
├── settings.opml
├── styles.css
└── code.js
├── LICENSE
├── readme.md
└── index.html
/pork.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scripting/electricPork/master/pork.icns
--------------------------------------------------------------------------------
/pork.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scripting/electricPork/master/pork.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "electricPork",
3 | "version": "1.5.0",
4 | "main": "main.js",
5 | "dependencies" : {
6 | "electronland": "*"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | const electronland = require ("electronland").main;
2 |
3 | var myConfig = {
4 | productname: "electricPork",
5 | productnameForDisplay: "Electric Pork",
6 | description: "The fastest way to tweet a storm, on your desktop.",
7 | version: "1.5.0",
8 | indexfilename: "index.html",
9 | flOpenDevToolsAtStart: false,
10 | mainWindowWidth: 1100,
11 | mainWindowHeight: 1000,
12 | appDirname: __dirname
13 | }
14 |
15 | electronland.init (myConfig, function () {
16 | });
17 |
--------------------------------------------------------------------------------
/code/homepage.js:
--------------------------------------------------------------------------------
1 | const shell = require ("electronland").shell;
2 |
3 | function startup () {
4 |
5 | const porkChopOptions = {
6 | productname: "electricPork",
7 | productnameForDisplay: "Electric Pork",
8 | urlChangeNotes: "http://scripting.com/2017/03/22/whatsNewInElectricPork061.html",
9 | flElectronShell: true
10 | };
11 | porkChopStartup (porkChopOptions);
12 |
13 | var shellOptions = {
14 | };
15 | shell.init (shellOptions, function () {
16 | if (twIsTwitterConnected ()) {
17 | twGetUserInfo (twGetScreenName (), function (userinfo) {
18 | console.log ("startup: userinfo == " + jsonStringify (userinfo));
19 | });
20 | }
21 | });
22 | }
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Dave Winer
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # electricPork
2 |
3 | An Electron app that helps you write and publish threads on Twitter.
4 |
5 | ### Download the app
6 |
7 | You can download the app that's built from this repo.
8 |
9 | There are links on that page to user-level docs and a place to ask questions about using the app. Do not post questions about working on the app there, use the Issues section of this repo.
10 |
11 | ### Why release Electric Pork as open source?
12 |
13 | There are two reasons, the second much more important than the first.
14 |
15 | 1. Writing and publishing threads on Twitter is a big deal. Twitter is adding the feature to the core product, but Electric Pork is more of an editor, it automates a bunch of things that Twitter makes you do by hand, such as wrapping tweets, numbering them, adding hash tags. It also creates an RSS feed of your threads, so they can be shared with other environments. Making the functionality open source means it can evolve in different ways, it could get interesting.
16 |
17 | 2. It's a demo app for the ElectronLand package, which hides most of the details of making an app run in Electron, and making it cross-platform. So far I've only mastered creating Mac apps with ElectronLand, but I want my apps to go to Linux and Windows, and perhaps other places that Electron might be going. By releasing Electric Pork as open source, and asking that people try to port it, we will get ports of the underlying framework. And that will make other projects be cross-platform as well. It's a way of getting ElectronLand thoroughly ported, if it works.
18 |
19 | ### Requirements
20 |
21 | In order to build Electric Pork, you need to install three things:
22 |
23 | 1. node.js.
24 |
25 | 2. electron.
26 |
27 | 3. electron-packager.
28 |
29 | Installing Node is easy, I've got a how-to for that on the 1999-project site.
30 |
31 | I am not an expert at installing electron and electron-packager, but I've managed to do it on my systems a few times.
32 |
33 | ### How to build
34 |
35 | 0. Download or clone the project from GitHub.
36 |
37 | 1. cd <the directory with the electricPork code>
38 |
39 | 2. npm install
40 |
41 | 3. sudo electron-packager . "Electric Pork" --platform=darwin --arch=all --electron-version=0.37.5 --overwrite --icon=pork.icns
42 |
43 | There should be an Electric Pork sub-directory, with an app inside. You can copy that anywhere, or run it from that location.
44 |
45 |
--------------------------------------------------------------------------------
/code/settings.opml:
--------------------------------------------------------------------------------
1 |
2 |
" + s + "
"; 269 | } 270 | $(idTweetArray).html (htmltext); 271 | } 272 | function setCharCount () { 273 | } 274 | function setTweetButtonText () { 275 | var s, len = tweetArray.length; 276 | if (len == 0) { 277 | s = "Nothing to tweet yet"; 278 | } 279 | else { 280 | s = "Post " + len + " Tweet"; 281 | if (len > 1) { 282 | s += "s"; 283 | } 284 | } 285 | $(idTweetButton).html (s); 286 | } 287 | function getUsersText () { 288 | return ($(idTextArea).val ()); 289 | } 290 | function recalcTweetArray (flMustRecalc) { 291 | var s = getUsersText (); 292 | if (flMustRecalc == undefined) { 293 | flMustRecalc = false; 294 | } 295 | if ((s != lastTextArrayContents) || flMustRecalc) { 296 | splitIntoTweetArray (s); 297 | viewTweetArray (); 298 | setCharCount (); 299 | localStorage.lastTweetText = s; 300 | lastTextArrayContents = s; 301 | whenLastKeystroke = new Date (); 302 | } 303 | } 304 | function sendOneTweet (theTweet, idInReplyTo, callback) { 305 | if (appConsts.flElectronShell) { 306 | if (idInReplyTo == 0) { 307 | idInReplyTo = undefined; 308 | } 309 | twTweet (theTweet.text, idInReplyTo, function (tweetData) { 310 | if (callback !== undefined) { 311 | callback (undefined, tweetData); 312 | } 313 | }); 314 | } 315 | else { 316 | twTweet (theTweet.text, idInReplyTo, function (tweetData) { 317 | if (callback !== undefined) { 318 | callback (undefined, tweetData); 319 | } 320 | }); 321 | } 322 | } 323 | function sendNextTweet () { 324 | var now = new Date (); 325 | if (flTweeting) { 326 | if (secondsSince (whenLastTweet) >= appPrefs.ctSecsBetwTweets) { //6/9/14 by DW 327 | function tweetNext () { 328 | var theTweet = tweetArray [ixNextItemToTweet]; 329 | whenLastTweet = now; 330 | console.log ("sendNextTweet: theTweet.text == " + theTweet.text); 331 | viewTweetArray (ixNextItemToTweet); 332 | sendOneTweet (theTweet, idLastTweet, function (err, tweetData) { 333 | if (err) { 334 | var theMessage = ""; 335 | try {theMessage = JSON.parse (err.data).errors [0].message;} catch (err) {}; 336 | alertDialog ("There was an error sending the tweet. " + theMessage); 337 | flTweeting = false; //tweeting stops after error -- 3/4/17 by DW 338 | viewTweetArray (); //take the bold off the item that caused the error -- 3/4/17 by DW 339 | } 340 | else { 341 | lastTweetData = tweetData; //for debugging 342 | idLastTweet = tweetData.id_str; 343 | 344 | if (theTweet.id === undefined) { 345 | theTweet.id = tweetData.id_str; 346 | if (flAddIdToHistory) { //6/18/14 by DW 347 | 348 | var itemLink = "https://twitter.com/" + localStorage.twScreenName + "/status/" + idLastTweet; 349 | appPrefs.rssHistory [0].link = itemLink; 350 | appPrefs.rssHistory [0].guid = { 351 | value: itemLink, 352 | flPermalink: true 353 | }; 354 | appPrefs.rssHistory [0].id = idLastTweet; 355 | 356 | flAddIdToHistory = false; //consume 357 | prefsToStorage (); 358 | } 359 | } 360 | else { 361 | console.log ("sendNextTweet: caught it trying to overwrite " + theTweet.id + " with " + tweetData.id_str); 362 | } 363 | 364 | console.log ("sendNextTweet: theTweet.text == " + theTweet.text + " (after sending the tweet)"); 365 | } 366 | }); 367 | } 368 | function endTweets () { 369 | if (flTweeting) { 370 | viewTweetArray (); //with nothing bold 371 | flTweeting = false; 372 | idLastTweet = 0; //first tweet in next sequence is in reply to nothing 373 | $(idTextArea).select (); //select all the text for easy deleting 374 | if (appPrefs.flPublishRss) { 375 | lpcBuildRssFeed (); 376 | } 377 | } 378 | } 379 | if (isReverseChronologicOrder ()) { 380 | if (ixNextItemToTweet >= 0) { 381 | tweetNext (); 382 | ixNextItemToTweet--; 383 | } 384 | else { 385 | endTweets (); 386 | } 387 | } 388 | else { 389 | if (ixNextItemToTweet < tweetArray.length) { 390 | tweetNext (); 391 | ixNextItemToTweet++; 392 | } 393 | else { 394 | endTweets (); 395 | } 396 | } 397 | } 398 | } 399 | } 400 | function startTweeting () { 401 | function doStart () { 402 | var s = getUsersText (); 403 | addToHistory (s); //6/18/14 by DW 404 | flAddIdToHistory = true; //6/18/14 by DW 405 | if (isReverseChronologicOrder ()) { 406 | ixNextItemToTweet = tweetArray.length - 1; 407 | } 408 | else { 409 | ixNextItemToTweet = 0; 410 | } 411 | flTweeting = true; 412 | whenLastTweet = new Date (new Date () - appPrefs.ctSecsBetwTweets * 60 * 1000); //so the first one goes out right away 413 | } 414 | if (appConsts.flElectronShell) { 415 | doStart (); 416 | } 417 | else { 418 | if (appConsts.flWhitelistEnabled) { //10/13/20 by DW -- disable whitelisting 419 | if (twIsTwitterConnected ()) { 420 | twUserWhitelisted (localStorage.twScreenName, function (flwhitelisted) { 421 | if (flwhitelisted) { 422 | doStart (); 423 | return; 424 | } 425 | else { 426 | alertDialog ("Can't post because \"" + localStorage.twScreenName + "\" is not whitelisted."); 427 | } 428 | }); 429 | } 430 | } 431 | else { 432 | doStart (); 433 | } 434 | } 435 | } 436 | function updateTwitterButton () { 437 | var buttontext = (twIsTwitterConnected ()) ? "Sign off Twitter" : "Sign on Twitter"; 438 | $(idTwitterButton).html (twittericon + " " + buttontext); 439 | 440 | //set screen name 441 | var name = localStorage.twScreenName; 442 | var url = "http://twitter.com/" + name; 443 | var script = "openUrl ('" + url + "')"; 444 | var linktoname = "" + name + "" 445 | linktoname = (twIsTwitterConnected ()) ? linktoname : ""; 446 | $(idScreenName).html (linktoname); 447 | } 448 | function showAboutDialog () { 449 | $('#idInfoDialog').modal ("show"); 450 | } 451 | function showPrefsDialog () { 452 | prefsDialogShow (); 453 | } 454 | function openUrl (url) { 455 | if (appConsts.flElectronShell) { 456 | shell.openLinkInExternalWindow (url); 457 | } 458 | else { 459 | window.open (url); 460 | } 461 | } 462 | function toggleConnect () { 463 | if (appConsts.flElectronShell) { 464 | shell.toggleTwitterConnect () 465 | } 466 | else { 467 | twToggleConnectCommand (); 468 | } 469 | } 470 | function loadPrefsDialogHtml () { //3/9/17 by DW 471 | readHttpFile (urlPrefsDialogHtml, function (htmltext) { 472 | if (htmltext === undefined) { 473 | console.log ("loadPrefsDialogHtml: error loading the HTML."); 474 | } 475 | else { 476 | console.log ("loadPrefsDialogHtml: htmltext.length == " + htmltext.length); 477 | $("#idPrefsDialogPlace").html (htmltext); 478 | } 479 | }); 480 | } 481 | function porkChopEverySecond () { 482 | sendNextTweet (); 483 | recalcTweetArray (); 484 | updateTwitterButton (); 485 | setTweetButtonText (); 486 | $(idTextArea).autoGrow (); //3/11/17 by DW 487 | if (flMaxTweetsError) { 488 | flMaxTweetsError = false; 489 | alertDialog ("Too many tweets in this storm. Maximum is " + maxTweetsPerStorm + "."); 490 | } 491 | } 492 | function porkChopGetUserInfo () { 493 | twGetUserInfo (localStorage.twScreenName, function (userData) { 494 | twitterUserInfo = userData; 495 | if (!appPrefs.flRssPrefsInitialized) { 496 | appPrefs.rssTitle = twitterUserInfo.name; 497 | appPrefs.rssDescription = twitterUserInfo.description; 498 | appPrefs.flRssPrefsInitialized = true; 499 | appPrefs.rssLink = "https://twitter.com/" + localStorage.twScreenName; 500 | prefsToStorage (); 501 | } 502 | }); 503 | } 504 | function porkChopStartup (options) { 505 | function updateVersionNumber () { 506 | var s = "v" + appConsts.version; 507 | if (appConsts.urlChangeNotes.length > 0) { 508 | 509 | var theScript = "openUrl ('" + appConsts.urlChangeNotes + "')"; 510 | s = "" + s + ""; 511 | 512 | } 513 | $("#idVersionNumber").html (s); 514 | } 515 | console.log ("porkChopStartup"); 516 | for (var x in options) { 517 | appConsts [x] = options [x]; 518 | } 519 | twStorageData.urlTwitterServer = appConsts.urlTwitterServer; //1/16/16 by DW 520 | urlDefaultServer = appConsts.urlTwitterServer; //"http://twitter.porkchop.io/"; 521 | //check if we're starting up on a phone 522 | function hideSection (id) { 523 | document.getElementById (id).style.display = "none"; 524 | } 525 | if ($("#idTellTalePart").css ("visibility") == "visible") { //phone version -- 6/8/14 by DW 526 | idTextArea = "#idPhoneTextArea"; 527 | idTweetArray = "#idPhoneTweetArray"; 528 | idTweetButton = "#idPhoneTweetButton"; 529 | idProductName = "#idPhoneProductName"; 530 | idTwitterButton = "#idPhoneTwitterButton"; 531 | idScreenName = "#idPhoneScreenName"; 532 | 533 | hideSection ("idTextAndControls"); 534 | } 535 | else { 536 | hideSection ("idPhoneTextAndControls"); 537 | } 538 | storageToPrefs (); 539 | 540 | if (appConsts.flElectronShell) { //2/20/17 by DW 541 | porkChopGetUserInfo (); 542 | } 543 | else { 544 | twGetTwitterConfig (); //7/15/14 by DW 545 | twGetOauthParams (); 546 | if (twIsTwitterConnected ()) { //7/30/14 by DW 547 | twUserWhitelisted (localStorage.twScreenName, function (flwhitelisted) { 548 | if (!flwhitelisted) { 549 | confirmDialog ("\"" + localStorage.twScreenName + "\" is not authorized to use this app.", function (flconfirm) { 550 | window.open ("http://littlepork.smallpict.com/2014/11/19/littlePorkChopComingBack.html"); 551 | }); 552 | } 553 | }); 554 | porkChopGetUserInfo (); 555 | } 556 | } 557 | 558 | var myFakeUrl = "http://pork.io/code?v=" + appConsts.version; 559 | hitCounter (undefined, undefined, myFakeUrl, myFakeUrl); 560 | document.title = appConsts.productnameForDisplay; 561 | $(idProductName).html (appConsts.productnameForDisplay); 562 | updateVersionNumber (); 563 | updateTwitterButton (); 564 | { //set up the text editing area 565 | var s = ""; 566 | if (localStorage.lastTweetText != undefined) { 567 | s = localStorage.lastTweetText; 568 | } 569 | $(idTextArea).html (s); 570 | } 571 | { //set up the tweet array 572 | splitIntoTweetArray (s); 573 | viewTweetArray (); 574 | } 575 | setCharCount (); 576 | setTweetButtonText (); 577 | $(idTextArea).autoGrow (); 578 | linkWarning (); //11/1/17 by DW 579 | porkChopEverySecond (); //3/11/17 by DW -- do the first call immediately 580 | self.setInterval (porkChopEverySecond, 1000); //call every second 581 | if (appConsts.flElectronShell) { //10/24/17 by DW 582 | shell.openLinksInExternalWindow (); 583 | } 584 | } 585 | --------------------------------------------------------------------------------