Giter Club home page Giter Club logo

markdown-hitcounter's Introduction

Markdown Hit Counter

This counter is intended for use in GitHub README markdown files. It has the following features:

  • It is accessed in a <img> tag in the markdown file.
  • It will only count for specified IDs. IDs are a means to identify which README was viewed.
  • It does not display the current count. This is intentional.
  • It's small, less than 115kb in size.
    • Code + JSON = ~4k
    • Images = ~111k - The only reason it's this big is because of the "error" images, they're supposed to be obvious. The actual image that used in counting is less than 1k in size.

Run Time Requirements

You will need:

  • A web server, HTTP or HTTPS should be ok.
  • The server needs PHP >5.6.
  • You will need access to your server to copy files and create a folder.

Set Up

These set up instructions will verify that it's working. However for live use some editing is required, this will be described later.

1) Copy the following files to a desired location on your server. Typically this will be in a public_html folder or in a sub-folder(recommended) of public_html.

  • counters.json
  • mdcount.php
  • All PNG files, blank-10x10.png, err-10x10.png, id_err.png, id_oops.png, and test-246x246.png
  • NOTE: It is not necessary to copy the mdimg folder to the server!

2) In the folder where you copied the files to create a new folder named logs.

Test Run

Open example.html in your editor and:

1) Find the <img> tags and edit the src attributes.

  • Replace all occurrences of [your-server]/[some-folder] with your server's domain and the sub-folder where you placed the files.

2) Then on your PC run the example.html file by double-clicking it or by dropping it onto an open browser window.

Test Run Expected Results:

a) At the top of the page you should see this:

b) A little further down the page you will see where there does not appear to be an image. It's there, it's just small (10px X 10px) and transparent.

c) The last two images demonstrate how errors are handled. The two types of errors that can occur are:

  • An invalid ID
  • The ID query is missing

The images are large on purpose. They're meant to be obvious when something is wrong.

d) In the folder where you placed the files you will have a new file named testtest_count.log.

e) In the sub-folder named logs you should have 2 new files, example_1_count.log and example_2_count.log.

Each of the *.log files should have a count of 1 in them.

Counter Log Files

The counter log files will be found in the folder ./logs. They are JSON formatted and contain the current count and the date and time of the visit.

Here is an example:

{
  "count": 2,
  "time": 1616961746,
  "dtime": [
    "20210328",
    "150226"
  ]
}

Markdown Usage

Use within a GitHub markdown file is easy. You cah add the counter to a README.md file or any other *.md file in any of your repositories.

The following line can go anywhere in the markdown file but at the bottom is recommended.

<img src="http://[your-server]/[some-folder]/mdcount.php?id=testtest">

Edit counters.json

The counters.json file contains IDs that insure that each use has its own counter log file. The counters.json file in this repository looks like this:

{
  "valid":[
    "TESTTEST",
    "example_1",
    "example_2"
  ]
}

Then:

<img src="http://[your-server]/[some-folder]/mdcount.php?id=example_1">

will create this file - logs/example_1_count.log, and each time the containing file is opened it will increment the count in that log file.

Let's say you have a README in your repository named My Repo. First add it to the counters.json file like this:

{
  "valid":[
    "TESTTEST",
    "example_1",
    "example_2",
    "My_Repo"
  ]
}

And to count use this:

<img src="http://[your-server]/[some-folder]/mdcount.php?id=My_Repo">

The resulting file will be logs/my_repo_count.log.

NOTE: Replace any spaces with an underscore or hyphen. The incoming IDs and those in the counters.json file can be mixed case because they are converted to lower case before they're checked. For typical use with Github and markdown files the IDs should match repository names.

Edit images.json (optional)

The images.json file contains the file names of the PNG files and their path relative to the sub-folder where you placed the images and mdcounter.php.

{
    "path": "./",
    "testimg": "test-246x246.png",
    "countimg": "blank-10x10.png",
    "errimg": "id_err.png",
    "oopsimg": "id_oops.png"
}

The "path": "./" member may also be edited if necessary.

Things that Trigger the Counter

Other than a "normal view" here are somethings that will trigger the counter:

  • WYSIWYG markdown editors. Each time you open the markdown file it will trigger a count.
  • Refreshing GitHub repository main page while viewing it.

Count Data Retrieval

It is possible to retrieve the counter data for a single counter or for all of them. This can be accomplished with -

Return data for a specific counter -

GET http[s]://your-server/path-to-file/mdcountdata.php?id=sensornet

The data is returned in JSON -

[
    {
        "id": "sensornet",
        "data": {
            "count": 30,
            "time": 1616458038,
            "dtime": [
                "20210322",
                "190718"
            ]
        }
    }
]

Return just the count data for a specific counter -

GET http[s]://your-server/path-to-file/mdcountdata.php?id=sensornet&count

A single value is returned, no JSON formatting -

30

Return data for all counters -

GET http[s]://your-server/path-to-file/mdcountdata.php

The data is returned in JSON -

[
    {
        "id": "sensornet",
        "data": {
            "count": 30,
            "time": 1616458038,
            "dtime": [
                "20210322",
                "190718"
            ]
        }
    },
    {
        "id": "test",
        "data": {
            "count": 1,
            "time": 1616458038,
            "dtime": [
                "20210322",
                "190718"
            ]
        }
    }
]

Sorting Counter Data -

The counter data can be sorted by count before returning it to the caller.

The data is returned in ascending order -

GET http[s]://your-server/path-to-file/mdcountdata.php?csort=a

The data is returned in descending order -

GET http[s]://your-server/path-to-file/mdcountdata.php?csort=d

One of the following sorts can be chosen:

  • csort - sort by count value
  • tsort - sort by time of last hit
  • isort - sort by counter ID

Limiting Number of Counters in Sort -

It is possible to limit the quantity of counters returned for any of the sorting queries.

GET http[s]://your-server/path-to-file/mdcountdata.php?csort=a|[&limit=[1-n]]

The value of limit can be from 1 to n. If n exceeds the number of counters then all counters will be returned for that sorting choice.

NOTE : limit only applies to sorting queries.

Errors -

If an error occurs it is also returned in JSON -

GET http[s]://your-server/path-to-file/mdcountdata.php?id=BLAH


[
    {
        "error": true,
        "msg": "BLAH not found in ./counters.json"
    }
]

Counter Reports

The following files are used in report generation and viewing:

  • mdreport.php - Retrieves the counter data and renders a Bootstrap 4.x table
    • mdhcreport.css - Additional required CSS for the table
    • embedreport.css - CSS required for an embedded report
    • reporthead.html - For non-embedded reports, this contains the page title and table heading
    • reportcaption.html - For non-embedded reports, this contains the table caption
    • mdreport-th.txt - Column heading text
    • stddefines.php - A collection of define() that make a number of PHP $_SERVER[] values available to the application. It contains components used for creating URLs to resources
  • report.html - The minimum required HTML/CSS and JavaScript/jQuery to render and display the table

Retrieving the Report -

The code that retreives the report and handles clicks on the column headings is contained in report.html. The necessary CSS can be found in mdhcreport.css.

<body>
    <div id="repout">
    </div>
<script>
document.addEventListener("DOMContentLoaded", () => {
    /*
        Send a GET request and invoke a 
        callback function when completed.
    */
    function httpGet(url, callback, tickle = false) {
        var xmlhttp = new XMLHttpRequest();
        xmlhttp.onreadystatechange = function() {
            if(this.readyState == 4 && this.status == 200) {
                var resp = this.responseText;
                callback(resp);
            }
        };
        // bypass caching, useful when retrieving resources
        // that change frequently
        if((tickle === true) && (url.includes('?') === false)) {
            url = url + '?_=' + new Date().getTime();
        }
        xmlhttp.open('GET', url, true);
        xmlhttp.send();
    };

    //////////////////////////////////////////////////////////////////////////
    // sorting types
    var sorts = ['?csort=','?isort=','?tsort='];
    // sort directions
    var sdirs = ['a','d'];
    // URL to report generator
    var url = './mdreport.php';
    // optional limiting qty of counters returned
    var limit = '&limit=10';
    // 
    var datasrc = url+sorts[2]+sdirs[1];//+limit;

    // create a table with mdreport.php and place 
    // it within an specified element ID
    function createTable(elemid, ds) {
        httpGet(ds, (resp) => {
            $(elemid).html(resp);
            // wait for a column in this table to be clicked
            $(elemid+' .orderhover').click((col) => {
                var target = {
                    id: col.target.id,
                    ix: col.target.dataset.ix,
                    order: col.target.dataset.order
                };
                $(document).trigger('newtable', target);
            });
        },false);
    };

    // render the table of counters
    createTable('#repout', datasrc);

    // when a column heading is clicked 
    $(document).on('newtable', (e, target) => {
        $('.orderhover').off('click');
        // build the URL and query...
        var datasrc = url+sorts[target.ix - 1] + (target.order === 'a' ? 'd' : 'a');// + limit;
        // erase the previous table
        $('#repout').html('');
        // go get it!
        createTable('#repout', datasrc);
    });
});
</script>
</body>

Sample Report Screen Shot - (via report.html)

The "Hit Count", "Repository" and "Last Counted" headings can be clicked to select sorting criteria and direction (ascending vs descending). When a column heading is clicked the report caption will change to reflect the choice.

A demonstration of the counter report can be seen here.

NOTES :

  • The links in the "Repository" column are built from the IDs found in counters.json. The ID text I used there is the name of the repository that the counter is intended for. The corresponding counter data files are named repo-name_counter.json.
  • When the report is viewed the data shown is current.
  • If the "Last Counted" column date and time seem to be incorrect then the time zone may need to be changed. Edit the timezone.json file to match your time zone.

"Embedded" Report

It is possible to retreive counter data without using report.html. The purpose is to embed the table in another page and to be able to apply some alternative CSS.

To retrieve it -

GET http[s]://your-server/path-to-file/mdreport.php?tsort=d&limit=5&embed

The response is going to contain some CSS for the table, and just the table itself. You can use a browser and go to http[s]://your-server/path-to-file/mdreport.php?tsort=d&limit=5&embed. You will see something like this-

NOTE: The table is not sortable like the one rendered with report.html.

Other Uses

You could count just about anything. All you need is to do a GET of mdcount.php with a proper query and you got a counter!

Conclusion

This started out as an interesting afternoon project. I created it because I could not find exactly what I needed in the many hit counters out there.

And this project has evolved since then with the addition (and changes) of JSON formatted counter files, and sorted counter data retrieval. And the latest change was adding a 5th column to contain some GitHub statistics.


markdown-hitcounter's People

Contributors

jxmot avatar

Watchers

 avatar

markdown-hitcounter's Issues

Implement "embed" method for reports

In the report add &embed the result will be just the formatted table without any other content. The column-sort and indicator arrows are disabled and invisible.

Useful for short(&limit=), sorted results.

PHP 8 Error - missing parentheses

Found when using PHP 8:

PHP Fatal error: Unparenthesized a ? b : c ? d : e is not supported. Use either (a ? b : c) ? d : e or a ? b : (c ? d : e) in mdcountdata.php on line 78

Add columns to table

  • stars - DONE
  • forks - DONE
  • watchers - shield.io does not have this one
  • could be a single column w/ all in a single cell

Invstigate: the pros / cons of moving the hit counts into a database

Currently, each individual use of the hit counter creates a simple text file that contains a number.

PROs:

  • Fast, the code is minimal and executes quickly

CONs:

  • No other information is retained when hit occurs, such as the date/time of the count.
  • Unable to determine any sort of trending on when hit counts are occuring.

Database Candidates:

The choice for a database will follow these criteria:

  • Must be hosted locally: I use a hosting service that currently provides MySQL. I've been working on setting up MongoDB to run on the same server(s).
  • A minimum of overhead code.
  • Easy to set up, and use for others. Since a "cloud" hosted database isn't going to be used there isn't any requirement to create any additional accounts for a database service.
  • Must have a small footprint. The intent here is to allow small platforms like Raspberry Pi, Beagle Board, etc, to be used as a hosting server for the counters.

So far there are two possible candidates:

MySQL

  • Pros:
    • Readily avilable on most hosting servers, in some cases MariaDB is in place of MySQL and works nearly identically.
  • Cons:
    • Some set up is required for the necessary table(s).

MongoDB Community

Not readily available on most hosting servers.

SleekDB

Found at https://github.com/rakibtg/SleekDB. Seems active.

Looks like a decent flat DB solution for PHP. But may be over complicated for use here.


Does not return error image...

If the query is missing altogether, there are some instances where appropriate image is not returned.

This bug only appeared when hosting the test hmtl file in XAMPP.

Investigate: see if github vs npmjs can be detected

Publishing an npm package reads the README file. Investigate and determine -

  • does NPM ever hit the counter?
    • every render?
    • only once?
  • if so, detect? use IP address?

If they can be detected then either ignore the npmjs hits or count them separately.

Hosting Server problem...

This is the second occurrence of:

  • mdreport.php -

    • file_get_contents() is timing out when mdcountdata.php is being called.
    • direct access to mdcountdata.php works without any problems

    This problem seems to coincide with a reboot of the server, the first occurrence cleared on its own.

Fix date/time

The date/time on shown in report.html is behind by 5 hours.

  • The dtime array is correct
  • The time data is correct

But when time is read from the counter and converted to strings then the time is behind by 5 hours.

Possible Solution:

In mdcount.php -

  • Format dtime for time/date separators and use it for reports
    • This has its own problems, all of the older counter files will be formatted incorrectly. This means that there will need to be code to detect the missing date/time separators and put them in as needed.

Elsewhere? -

  • Find & fix the problem, not sure where to start looking because all of the code on either end appears correct.

Investigate: Github "double taps"

It appears that for a single view the README file is loaded more than once. Check to see if this is true.

If true then implement a debounce to filter the unnecessary hits.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.