Giter VIP home page Giter VIP logo

fern's Introduction

fern

                            
                            
   :##  Federated  
   #. Editor  and  
   # Reader  of  News                                                               
 #####   ###    #:##: #:##: 
   #       :#   ##  # #  :#   /.\
   #    #   #   #     #   #  // \\
   #    #####   #     #   #  \\///
   #    #       #     #   #   \//
   #        #   #     #   # -=//=-
   #     ###:   #     #   #-=//==-
                            
                            

fern: a curses-based mastodon client modeled off usenet news readers & pine, with an emphasis on getting to 'timeline zero'

Fern screenshot

Fern depends upon Mastodon.py, and is tested on python 2.7 (though it ought to work on python 3 as well).

It was forked from my misc repo, so if you want to see the history of older commits, view it here.

Installation

	pip install Mastodon.py
	sudo make install

When you start fern for the first time, it will prompt you for your instance URL, email, and password. (To reset this, delete ~/.fern)

If you would like to import your existing history of posts, mentions, and favorites, type :import_history. This may take a long time.

Philosophy

Fern comes out of some discussions on the fediverse about how user interface design influences behavior & how certain kinds of norms developed around reading whole threads in usenet because of the design of news clients. The UI of fern is an experiment in trying to encourage users to read entire threads, read their entire timeline, and treat the fediverse more like a medium for serious discussion (i.e., to de-twitterize the fediverse's culture with regard to sealioning).

It also tries to fill in a gap between command-line based clients like tootstream (wherein it's very easy to miss part of a thread or reply to the wrong post because the user manages IDs) and heavy, push-centered experiences like the mastodon web interface (wherein automatic loading can interrupt user input, and where the timeline can quickly get large enough to slow down your whole computer). This is a niche is shares with the likes of brutaldon.

To these ends, there are a couple general rules fern follows:

  • navigation and display privileges new & meaningful content
  • no operations occur without user input
  • local cache is used whenever possible
  • commonly-used operations ought to be one or two keystrokes
  • the user shouldn't need to keep anything in mind other than the message they are reading or writing

More specifically:

  • favorites are not meaningful information, and are not displayed at all (although it is possible to perform them)
  • boosts are treated as duplicates of the original message (i.e., they inherit the 'read' status of the original message)
  • expanding a thread is a single-keystroke operation
  • skipping to the next or previous unread message is a single-keystroke operation, while navigating to the next or previous message regardless of read status requires also pressing shift
  • CWs' openness is toggled globally, but the content of CW'd messages is not visible in the left-hand pane -- so, it's faster to read long threads where every item is CW'd
  • messages are cached locally as soon as they are fetched or posted, and search runs against the local cache
  • fetching new messages requires the user to perform the fetch operation (F) or open a thread (t)
  • there is only one timeline pane (the left-hand pane), and new content is placed at the top of the timeline (even in events like thread expansion, where the entire thread is prepended to the top of the timeline)

About the local cache

Fern caches all toots immediately after fetching them, and checks this local cache first for display. This aggressive caching is necessary because Mastodon.py lazily fetches items from the ActivityPub server as they're needed for view, which can be quite slow -- too slow for search, and sometimes too slow for rapid reading of the timeline.

This local cache is represented as a hash table keyed by message ID, and while it's a single table in memory, it's stored in 10 chunks (based on the last digit of the ID). This segmentation makes saving quicker in cases where only a few messages have been fetched.

Messages are fetched and added to the local cache if the user requests a fetch (F), expands a thread (t), requests a particular message by ID (:open_tid), or imports history (:import_history). A save occurs after each manual fetch (once per timeline) or after expanding each thread, and it also occurs upon exit.

Viewing images & long messages

Fern has a pager facility (accessed by pressing g). Setting a pager allows one to scroll through long messages, and setting a browser allows viewing messages as HTML. If the environment variable BROWSER is set to w3m -T text/html -o auto_image=TRUE and your copy of w3m was built with image support, then you can view images attached to a toot in your terminal by pressing g. The BROWSER environment variable, if it exists, overrides the value of PAGER.

Setting up auto-CW rules

Fern has a facility for automatically applying CWs to incoming toots based on regular expression matching. If the original post has a CW, then this adds the new categorizations to the end. These CWs are added at display time, so they aren't searchable.

The rules are stored as a TSV, in the file called cwrules.tsv in your fern config directory (usually ~/.fern).

The first column of the TSV is a regular expression, and the second column is the CW tag to be applied. For instance:

twitter|(bir(d|b)|hell)site	Twitter mention
sex|dick|baloney pony	lewd
sad|depressed	MH(-)
^(.+)@(noagenda\.social|sealion\.club)	troll instance

Missing features

Fern does not supply the ability to follow, mute, or block other users. It also doesn't allow you to mute threads, edit profile information, or change the visibility of your toots. It does not perform client-side size limit checks. It does not attempt to render HTML -- it turns both links and images into raw URLs, turns <p> and <br> tags into newlines, and strips all other tags entirely -- so users who want to see properly-rendered HTML must use an HTML pager.

Fern was planned to support nuanced (killfile-like) blocking and filtering, but this has not yet been implemented.

We have no account-switching support. The most convenient way to do this right now is keep a copy of .fern for every account and swap them out, but this means that (for instance) search is limited by account.

Flaws

Sometimes, the mastodon API doesn't give us as many messages as we ask for, or truncates the range. If you don't use fern for a couple days, you're liable to miss notifications. Using the page facility can get around this, & in the future fetch ought to use it.

Fern isn't heavily tested on python3, and switching between python 2 and python 3 with the same configuration and cache can cause problems. Specifically, it can corrupt your cache, because of some changes to how strings work that are too hairy for me to fully understand.

While fern now works on 80x24 terminals, it will probably still break if your terminal is much smaller. (If this happens, you'll get an error like "Something happened: ERR in addstr()".)

What do users say?

Drew DeVault says that I'm a "moron" for supporting python2, and that my code is bad.

Feoh says "VERY cool"

IceWolf says "700 lines of code? For an entire Mastodon client? Nice! (:"

tedu says "Good luck! (Earnestly.)"

fern's People

Contributors

benharri avatar enkiv2 avatar jfmcbrayer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

fern's Issues

Crashes with `NameError: name 'unicode' is not defined`.

When I build this (using this AUR package), and then run fern, and then configure my login, afterwards it crashes with

Traceback (most recent call last):
  File "/usr/bin/fern", line 905, in <module>
    main()
  File "/usr/bin/fern", line 894, in main
    else: tl=getTimeline("all", verbose=True)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/bin/fern", line 644, in getTimeline
    for item in which.split(): tl.extend(getTimeline(item, **kw_args))
                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/bin/fern", line 666, in getTimeline
    tl=ensureCached(tl)
       ^^^^^^^^^^^^^^^^
  File "/usr/bin/fern", line 755, in ensureCached
    elif type(toot) in [str, unicode]: # TID
                             ^^^^^^^
NameError: name 'unicode' is not defined

This crash repeats when I want to start fern again.

Python version: 3.11.6.

Regards!

Error after logging in

I verified that this happens using Python 2.7 on Fedora linux. It fetches the timeline, displays the UI (with blank cards for toots), then displays the short error "Something happened: addstr() returned ERR". After typing any key, the following traceback:

  File "./fern", line 716, in main
    mainloop()
  File "./fern", line 671, in mainloop
    drawPanelContents(tl[tlIdx:], hlIdx)
  File "./fern", line 321, in drawPanelContents
    drawPanels(fillInDomain(mastodon.account_verify_credentials()["acct"]), ROWS, COLS, selectedIdx)
  File "./fern", line 271, in drawPanels
    scr.addstr(centerPad("[(b)oost] [(f)av] [mark_(u)nread] "+clopen+" [(t)hread_view] [(F)etch_new] [(:)_command]", cols).replace("_", " "), curses.color_pair(menunum))
error: addstr() returned ERR
Traceback (most recent call last):
  File "./fern", line 724, in <module>
    main()
  File "./fern", line 708, in main
    main()
  File "./fern", line 721, in main
    raise e
error: addstr() returned ERR

Jump to Oldest Unread

Getting to Timeline Zero makes a lot more sense if you start at the oldest unread toot, rather than reading everything backwards.

Reverse chronological is for firehoses you don't intend to drink everything from.

Crashes on Start due to NameError

fern/fern

Line 755 in 0244f1a

elif type(toot) in [str, unicode]: # TID

Raises

Traceback (most recent call last):
  File "/home/kensp/Documents/fern/./fern", line 905, in <module>
    main()
  File "/home/kensp/Documents/fern/./fern", line 894, in main
    else: tl=getTimeline("all", verbose=True)
  File "/home/kensp/Documents/fern/./fern", line 644, in getTimeline
    for item in which.split(): tl.extend(getTimeline(item, **kw_args))
  File "/home/kensp/Documents/fern/./fern", line 666, in getTimeline
    tl=ensureCached(tl)
  File "/home/kensp/Documents/fern/./fern", line 755, in ensureCached
    elif type(toot) in [str, unicode]: # TID
NameError: name 'unicode' is not defined

Crash on trying to open pager

Trying to open pager causes a crash.

Traceback (most recent call last):
  File "/home/kensp/Documents/fern/./fern", line 905, in <module>
    main()
  File "/home/kensp/Documents/fern/./fern", line 902, in main
    raise e
  File "/home/kensp/Documents/fern/./fern", line 896, in main
    try: mainloop()
  File "/home/kensp/Documents/fern/./fern", line 869, in mainloop
    return cmd(table[key])
  File "/home/kensp/Documents/fern/./fern", line 586, in execCommand
    subprocessRun(pager, processContents(tl[tlIdx+hlIdx]["content"]))
  File "/usr/lib/python3.10/subprocess.py", line 503, in run
    with Popen(*popenargs, **kwargs) as process:
  File "/usr/lib/python3.10/subprocess.py", line 780, in __init__
    raise TypeError("bufsize must be an integer")
TypeError: bufsize must be an integer

Add image support?

Have you thought about adding image support with the help of for example w3mimgdisplay?

Crash on trying to open Thread view

Opening Thread view crashes

Traceback (most recent call last):
  File "/home/kensp/Documents/fern/./fern", line 905, in <module>
    main()
  File "/home/kensp/Documents/fern/./fern", line 902, in main
    raise e
  File "/home/kensp/Documents/fern/./fern", line 896, in main
    try: mainloop()
  File "/home/kensp/Documents/fern/./fern", line 869, in mainloop
    return cmd(table[key])
  File "/home/kensp/Documents/fern/./fern", line 528, in execCommand
    toots=expandThread(tid)
  File "/home/kensp/Documents/fern/./fern", line 679, in expandThread
    ensureCached(tid)
  File "/home/kensp/Documents/fern/./fern", line 758, in ensureCached
    conditionalStatusMsg(dmsg, false, verbose, msg, suffix=suffix)
NameError: name 'false' is not defined

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.