Welcome Guest ( Log In | Register )

 
Closed TopicStart new topic
> Saving Links of Random Encounter Events for Later Visits, an implementation discussion

 
post Aug 10 2014, 04:29
Post #1
djackallstar



ดีjackallstar
**********
Group: Gold Star Club
Posts: 8,194
Joined: 23-July 14
Level 500 (Godslayer)


Warning: The following content might be a bit too techinical.
You may want to skip this post if you're not interested in programming-related (JavaScript) stuff.


In this post I'm going to introduce how to properly save links of Random Encounter events for later visits,
and all the details of how I implemented it in one of my scripts.
There are three topics in this post:
(1) The reasons to do so.
(2) Where to save such links?
(3) How to display such links to a user?

(1) The reasons to do so.

Why does one ever want to keep track of the URLs of Random Encounter events?
The first reason is that one can easily miss them when browing the galleries:
QUOTE(Dan31 @ Aug 7 2014, 16:32) *

Also, feature request: keep in memory unclicked RE links (you can easily miss them when browsing the galleries). And the links probably need to be clicked in order (?), so only display the older one. (I'm not sure how long a link is valid, is it till a more recent RE or DOTND is triggered?)

The second reason is that, let's say one is AFK for a long time, and when he is back at the computer, he can immediately engage in the battle of Random Encounter events, rather than having to wait for at most 30 minutes for a Random Encounter event to occur.
It is especially useful if one works in the morning and afternoon, and only has access to his computer in the evening.

(2) Where to save such links?

I decide to save those URLs to the disk rather than in memory, in order to make the data (semi-)persistent,
so that even if you accidentally close the web browser or your computer crashes unexpectedly,
you can still have access to the saved URLs.

What options do we have?

1) Cookies
In most modern web browsers, a domain can have at most (approximately) 4K size of cookies.
([www.ietf.org] RFC 2965 suggests that "at least 4096 bytes per cookie", though.)
Let's do some math:
One Random Encounter link has 129 characters,
after escape()-ing it and save it in a cookie, the cookie value has more or less 145 characters.
There are at most 24 such links everyday (correct me if I'm wrong), meaning that the total amount of characters needed is 145*24=3480,
which leaves not much room for other cookies. Uh-oh.

2) localStorage
Unlike cookies, one can save data with larger size using localStorage, but it's a little tricky to share informations between subdomains, even though there are some workarounds such as using iFrame (http://stackoverflow.com/questions/4026479/use-localstorage-across-subdomains).

3) GM_getValue() and GM_setValue()
This is proabably the best method among the three. One can easily share saved URLs between different domains (no same-origin policy YAY), and no limit to the size of data one wants to save. Its only downside is that one cannot print the saved URLs in the interactive shell provided by the web browser; with cookies or localStorage, I can easily pretty-print saved URLs using JSON.stringify(_variable_, null, '\t').

Having said that, I chose 1) as my solution.
I truncated the URL and only kept the base64-encoded part (the red part).
Let's do some math again:
One base64-encoded string contains 80 characters,
after escape()-ing it and save it in a cookie, the cookie value has more or less 82 characters.
Total = 82*24=1968, which is much less than 4K, good.

(3) How to display such links to a user?

According to Dan31, it's better to display the saved URLs from the oldest to the newest.
Hence it's a FIFO scenario, and the best data structure to store these URLs would be a queue:
Just push() URLs to an array, and later access the array from index 0 to length-1.

Next thing would be retrieving the date information from the URL and display it to the user.

Let's take a look at an example URL of a Random Encounter event:
http://hentaiverse.org/?s=Battle&ss=ba&encounter=MTk4ODQ3MS0xNDA3NDEzOTg3LTMzMGQ4N2JiOWYyOTViZWU3ZmVhYzA0ZGUzMDVlNGIxNmJiMWZlZGY=

The red part is a base64-encoded string. After decoding it using the atob() function provided by most modern web browsers, we get the following string:
1988471-1407413987-330d87bb9f295bee7feac04de305e4b16bb1fedf

The blue part is a user ID, which is unique to every user on the forum. In this case it is my user ID. https://forums.e-hentai.org/index.php?showuser=1988471

The purple part is a string produced by some hash function.
Since it's almost impossible for users to produce such string (because they have no clue about the hash function),
this string can prevent users from auto-generating any valid URL of a Random Encounter event.

Finally, the green part. It's the only part we want.
The green part is a POSIX timestamp, also called Epoch time, it's the number of seconds since 1970/01/01 00:00:00.
This is the time this specific Random Encounter event has been created by the E-Hentai server,
and it has nothing to do with whether or not you have clicked the link of the Random Encounter event.
1407413987 is also what I see in the "event" cookie when I visit e-hentai.org or its subdomains (.e-hentai.org),
and that cookie will be set by the server to another value
only when another event (a Random Encounter event or "The Dawn of a New Day" event) happens.
It can be converted to human-readable data using the Date object in JS:

var epoch = '1407413987';
var da = new Date();
da.setTime(parseInt(epoch)*1000); // setTime takes milliseconds as its argument.
da = da.toLocaleTimeString();

"da" now contains a localized time string such as "Thu, 07 Aug 2014 12:19:47 GMT".
At this moment, we can just:

var a = document.createElement('A');
a.href = 'http://hentaiverse.org/?s=Battle&ss=ba&encounter=' + e; // e being the base64-encoded string (the red part)
a.target = '_blank';
a.text = da;

And then add "a" to a DIV or something for display.


This post has been edited by djackallstar: Aug 10 2014, 11:38
User is offlineProfile CardPM
Go to the top of the page
+Quote Post

 
post Aug 10 2014, 04:52
Post #2
lichtenlade



left hand attained the Dao
*******
Group: Gold Star Club
Posts: 1,208
Joined: 14-July 10
Level 488 (Godslayer)


I havent tried saving them but I think the RE link expires if we dont click it?
since it would overwrite the earlier generated ones

saving and collection RE links is good and all but
wouldnt it be an ethical issue that defeats the purpose of the random encounter feature?

Im looking forward to have that script released if it gets out of the gray zone (IMG:[invalid] style_emoticons/default/biggrin.gif)
User is offlineProfile CardPM
Go to the top of the page
+Quote Post

 
post Aug 10 2014, 05:07
Post #3
djackallstar



ดีjackallstar
**********
Group: Gold Star Club
Posts: 8,194
Joined: 23-July 14
Level 500 (Godslayer)


QUOTE(lichtenlade @ Aug 10 2014, 10:52) *

I havent tried saving them but I think the RE link expires if we dont click it?
since it would overwrite the earlier generated ones

It won't expire immediately (according to my test), but it will expire eventually.
I have no idea how the mechanism works, but one can have two RE battles in a row for sure.

QUOTE(lichtenlade @ Aug 10 2014, 10:52) *

saving and collection RE links is good and all but
wouldnt it be an ethical issue that defeats the purpose of the random encounter feature?
Im looking forward to have that script released if it gets out of the gray zone (IMG:[invalid] style_emoticons/default/biggrin.gif)

Oops... Hope I'm not in trouble with my script...

This post has been edited by djackallstar: Aug 10 2014, 05:08
User is offlineProfile CardPM
Go to the top of the page
+Quote Post

 
post Aug 10 2014, 05:18
Post #4
lichtenlade



left hand attained the Dao
*******
Group: Gold Star Club
Posts: 1,208
Joined: 14-July 10
Level 488 (Godslayer)


it just needs to be peer reviewed or a Nod from Tenbo

dont worry ^^;
User is offlineProfile CardPM
Go to the top of the page
+Quote Post

 
post Aug 10 2014, 05:20
Post #5
walkleft



Casual Poster
****
Group: Members
Posts: 303
Joined: 28-September 11
Level 347 (Dovahkiin)


As far as I am aware, the trick does not work if you try to save 3~4 random encounter or more. The first link did not work, and I only got the most recent link working.
User is offlineProfile CardPM
Go to the top of the page
+Quote Post

 
post Aug 10 2014, 08:00
Post #6
ctxl



バカ
****
Group: Members
Posts: 424
Joined: 20-May 12
Level 455 (Godslayer)


QUOTE(djackallstar @ Aug 9 2014, 20:07) *
It won't expire immediately (according to my test), but it will expire eventually.
I have no idea how the mechanism works, but one can have two RE battles in a row for sure.
Oops... Hope I'm not in trouble with my script...

If things haven't changed since December, they expire an hour after being generated.

There hasn't been any backlash from HVRED (which stores RE links found on the front page), so you should be fine.
User is offlineProfile CardPM
Go to the top of the page
+Quote Post

 
post Aug 10 2014, 20:13
Post #7
holy_demon



Osananajimi<3
*********
Group: Gold Star Club
Posts: 5,417
Joined: 2-April 10
Level 500 (Godslayer)


I use localStorage to store data in my private script, and maintain a bi-directional communication between EH and HV page. Information (time and link) is encoded into the URL. Page is open with window.open() and will be quickly closed when the script is done with it. I can use iframe but it does everything silence and it's very hard to debug.

I can store 3 links after which I must use the 1st link immediately or it will expires and consume one use from your daily limit (24 - I once left my window open before going to sleep and collected all 24 links, needless to say I can't do any RE that day). Do RE whenever you have 2 links just to be on the safe side

Feel free to copy pasta my code below if you like. Code re-usability is a good thing (IMG:[invalid] style_emoticons/default/tongue.gif)

Code Snippets:

LocalStorage structure
CODE

   var REdata = JSON.parse(localStorage.REData || "null");
   if (!REdata) {
      REdata = {time: Date.now(), link: []};
      sync();
   }
   function sync() {
     //store data in localStorage
      localStorage.REData = JSON.stringify(REdata);
   }


HV page to auto open EH page and receiving links sent from EH
CODE

   function hvHandle() {
      //capturing links sent by EH
      if (/(time|link)=/.exec(location.search)) {
         var timeMatch = /time=([^&=]+)/.exec(location.search);
         var linkMatch = /link=([^&=]+)/.exec(location.search);
         if (timeMatch) {
            REdata.time = Number(decodeURIComponent(timeMatch[1]));
            console.log("receive time", Number(decodeURIComponent(timeMatch[1])));
         }
         if (linkMatch) {
            REdata.link.push(decodeURIComponent(linkMatch[1]));
            console.log("receive link", decodeURIComponent(linkMatch[1]));
         }
         sync();
         window.close();
      } else if (Date.now() - REdata.time > 1860000 || Math.floor((Date.now() + 60000) / 86400000) !== Math.floor(REdata.time / 86400000)) {
         //auto open EH page to retrieve links
         //lagging by 60s to give EH page priority to reload
         window.open("http://e-hentai.org/?autoclose", "RE", "width=400,height=320");
         REdata.time = Date.now();
         sync();
      }
   }


EH page to send link to HV page, and auto-refresh
CODE

   function ehHandle() {
      var eventpane = document.querySelector("#eventpane");
      REdata.time = Number(/event=([^;]+)/.exec(document.cookie)[1] + "000");
      if (eventpane) {
         var ahref = eventpane.querySelector("a");
         if (ahref) {
            var link = [];
            link.push(/'([^']+?)'/.exec(ahref.getAttribute("onclick"))[1]);
            window.open("http://hentaiverse.org/?" + "time=" + encodeURIComponent(REdata.time) + "&link=" + encodeURIComponent(link[link.length - 1]), "HV", "width=400,height=320");
            console.log("Capture link", link[0]);
         } else {
            window.open("http://hentaiverse.org/?" + "time=" + encodeURIComponent(REdata.time), "HV", "width=400,height=320");
            console.log("Dawn of new Day")
         }
         sync();
      } else if (location.search === "?autoclose") {
         //auto close and send a time update if called by a HV page
         window.open("http://hentaiverse.org/?" + "time=" + encodeURIComponent(REdata.time), "HV", "width=400,height=320");
         console.log("Update Time");
         window.close();
      }
      //auto refresh EH after an interval
      window.setTimeout(function() {
         location.href = "";
      }, Math.min(1800000 + Number(REdata.time), Math.ceil(REdata.time / 86400000) * 86400000) - Date.now());
   }



Anyway I also implemented self-destructed ninja iframe function, you can replace window.open with this. Does the same thing
CODE

function getPage(url) {
      var iframe = document.body.appendChild(document.createElement('iframe'));
      iframe.style.cssText = 'width: 0px; height: 0px; overflow: hidden; white-space: nowrap; visibility: hidden;';
      iframe.onload = function() {
         document.body.removeChild(this);
         delete this;
         if (location.search === "?autoclose") {
            window.close();
         }
      }
      iframe.src = url;
      console.log(iframe);
   }


This post has been edited by holy_demon: Aug 10 2014, 20:32
User is offlineProfile CardPM
Go to the top of the page
+Quote Post

 
post Aug 11 2014, 06:35
Post #8
djackallstar



ดีjackallstar
**********
Group: Gold Star Club
Posts: 8,194
Joined: 23-July 14
Level 500 (Godslayer)


Thanks for sharing the code, I see your idea.
I suggest you use GM_getValue() and GM_setValue(), though;
with these GM APIs you will need to call sync() only on e-hentai.org, and the URL looks cleaner.
User is offlineProfile CardPM
Go to the top of the page
+Quote Post

 
post Aug 11 2014, 07:18
Post #9
el h



It's all about the Pentiums, baby!
*******
Group: Members
Posts: 1,328
Joined: 10-November 09
Level 402 (Ascended)


QUOTE(djackallstar @ Aug 10 2014, 04:29) *

One Random Encounter link has 129 characters,
after escape()-ing it and save it in a cookie, the cookie value has more or less 145 characters.
There are at most 24 such links everyday (correct me if I'm wrong), meaning that the total amount of characters needed is 145*24=3480,
which leaves not much room for other cookies. Uh-oh.

You can compress the urls, because you have information about them.

You can reconstruct the url from the following data:
- userid, you can get from another cookie i presume
- timestamp, can be compressed to 32 bit
- hash, is 160 bit
That means you need to store 192 bit per link. Using base64, that is only 32 characters.



QUOTE(holy_demon @ Aug 10 2014, 20:13) *

CODE

    delete this;


My Ecmascript is a little rusty, but that does not look like a valid statement.
User is offlineProfile CardPM
Go to the top of the page
+Quote Post

 
post Aug 11 2014, 12:11
Post #10
SPoison



Veteran Poster
********
Group: Gold Star Club
Posts: 3,937
Joined: 20-July 10
Level 500 (Ponyslayer)


I DIDN'T READ ANY OF THE ABOVE.

Pretty sure you can only hold 2 links at a time. I'm not sure where I heard it but I think 1 hour is how long a link will last so by the time you have a 3rd the 1st would have expired. So you know...keep that in mind.
User is offlineProfile CardPM
Go to the top of the page
+Quote Post

 
post Aug 11 2014, 14:57
Post #11
holy_demon



Osananajimi<3
*********
Group: Gold Star Club
Posts: 5,417
Joined: 2-April 10
Level 500 (Godslayer)


QUOTE(djackallstar @ Aug 11 2014, 14:35) *

Thanks for sharing the code, I see your idea.
I suggest you use GM_getValue() and GM_setValue(), though;
with these GM APIs you will need to call sync() only on e-hentai.org, and the URL looks cleaner.


I wrote it back when I still used Opera, so no GM functions (IMG:[invalid] style_emoticons/default/smile.gif)

QUOTE(el h @ Aug 11 2014, 15:18) *

You can compress the urls, because you have information about them.

You can reconstruct the url from the following data:
- userid, you can get from another cookie i presume
- timestamp, can be compressed to 32 bit
- hash, is 160 bit
That means you need to store 192 bit per link. Using base64, that is only 32 characters.
My Ecmascript is a little rusty, but that does not look like a valid statement.


You're right. For some reason I always typed out delete <something> whenever I'm dealing with iframe. It's a bad habit. Fortunately, it's pretty much harmless I use it on non-attribute value
User is offlineProfile CardPM
Go to the top of the page
+Quote Post

 
post Aug 20 2014, 15:43
Post #12
dnbdave



Now hooning: Subaru Rex 2.0 (flipped the 1.0)
*******
Group: Gold Star Club
Posts: 2,009
Joined: 16-June 08
Level 482 (Godslayer)


Short of the technical implementation (getting over the "can I be assed to do this" hump would be challenge number 1 for this) I'm going to argue that there is probably too much potential for abuse in opening the system to the possibility of caching RE's and replaying them or playing them on demand.

As Jenga said, they're sessions which are expiring after an hour. If that's the current implementation then there's likely a good reason for this.

Nice thought though, and excellent post.
User is offlineProfile CardPM
Go to the top of the page
+Quote Post


Closed TopicStart new topic
1 User(s) are reading this topic (1 Guests and 0 Anonymous Users)
0 Members:

 


Lo-Fi Version Time is now: 28th April 2024 - 11:45